/
domain-model-rename-refactoring.ts
109 lines (99 loc) · 4.61 KB
/
domain-model-rename-refactoring.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { AstNode, LangiumDocument, LangiumDocuments, ReferenceDescription, URI } from 'langium';
import { AstUtils, CstUtils, isNamed } from 'langium';
import { DefaultRenameProvider } from 'langium/lsp';
import type { RenameParams } from 'vscode-languageserver-protocol';
import { Location, TextEdit, type Range, type WorkspaceEdit } from 'vscode-languageserver-types';
import type { DomainModelServices } from './domain-model-module.js';
import type { QualifiedNameProvider } from './domain-model-naming.js';
import { isPackageDeclaration } from './generated/ast.js';
export class DomainModelRenameProvider extends DefaultRenameProvider {
protected readonly langiumDocuments: LangiumDocuments;
protected readonly qualifiedNameProvider: QualifiedNameProvider;
constructor(services: DomainModelServices) {
super(services);
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
this.qualifiedNameProvider = services.references.QualifiedNameProvider;
}
override async rename(document: LangiumDocument, params: RenameParams): Promise<WorkspaceEdit | undefined> {
const changes: Record<string, TextEdit[]> = {};
const rootNode = document.parseResult.value.$cstNode;
if (!rootNode) return undefined;
const offset = document.textDocument.offsetAt(params.position);
const leafNode = CstUtils.findDeclarationNodeAtOffset(rootNode, offset, this.grammarConfig.nameRegexp);
if (!leafNode) return undefined;
const targetNode = this.references.findDeclaration(leafNode);
if (!targetNode) return undefined;
if (isNamed(targetNode)) targetNode.name = params.newName;
const location = this.getNodeLocation(targetNode);
if (location) {
const change = TextEdit.replace(location.range, params.newName);
const uri = location.uri;
if (uri) {
if (changes[uri]) {
changes[uri].push(change);
} else {
changes[uri] = [change];
}
}
}
for (const node of AstUtils.streamAst(targetNode)) {
const qn = this.buildQualifiedName(node);
if (qn) {
const options = { onlyLocal: false, includeDeclaration: false };
this.references.findReferences(node, options).forEach(reference => {
const nodeLocation = this.getRefLocation(reference);
const isQn = this.hasQualifiedNameText(reference.sourceUri, reference.segment.range);
let newName = qn;
if (!isQn) newName = params.newName;
const nodeChange = TextEdit.replace(nodeLocation.range, newName);
if (changes[nodeLocation.uri]) {
changes[nodeLocation.uri].push(nodeChange);
} else {
changes[nodeLocation.uri] = [nodeChange];
}
});
}
}
return { changes };
}
protected hasQualifiedNameText(uri: URI, range: Range): boolean {
const langiumDoc = this.langiumDocuments.getDocument(uri);
if (!langiumDoc) {
return false;
}
const rangeText = langiumDoc.textDocument.getText(range);
return rangeText.includes('.', 0);
}
getRefLocation(ref: ReferenceDescription): Location {
return Location.create(
ref.sourceUri.toString(),
ref.segment.range
);
}
getNodeLocation(targetNode: AstNode): Location | undefined {
const nameNode = this.nameProvider.getNameNode(targetNode);
if (nameNode) {
const doc = AstUtils.getDocument(targetNode);
return Location.create(
doc.uri.toString(),
CstUtils.toDocumentSegment(nameNode).range
);
}
return undefined;
}
protected buildQualifiedName(node: AstNode): string | undefined {
if (node.$type === 'Feature') return undefined;
let name = this.nameProvider.getName(node);
if (name) {
if (isPackageDeclaration(node.$container)) {
name = this.qualifiedNameProvider.getQualifiedName(node.$container, name);
}
}
return name;
}
}