Skip to content

Commit 63db792

Browse files
committed
ls(fix): improve text insertion for attributes
1 parent 9574d38 commit 63db792

File tree

3 files changed

+74
-5
lines changed

3 files changed

+74
-5
lines changed

server/src/documents.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ export interface TextDocumentEvent {
129129
document: TextDocumentIdentifier;
130130
}
131131

132+
export interface TextDocumentLine {
133+
text: string;
134+
line: number;
135+
start: number;
136+
}
137+
132138
export class TextDocuments {
133139
private projectService: ProjectService;
134140
private logger: ProjectLoggerImpl;
@@ -214,6 +220,16 @@ export class TextDocuments {
214220
return [];
215221
}
216222

223+
public getDocumentLine(document: TextDocumentIdentifier, offset: number): TextDocumentLine {
224+
const info = this.getServiceInfo(document);
225+
if (info) {
226+
const lineInfo = this.projectService.positionToLineOffset(info.fileName, offset);
227+
if (lineInfo) {
228+
return { line: lineInfo.line, start: offset - lineInfo.offset, text: lineInfo.text };
229+
}
230+
}
231+
}
232+
217233
public getNgService(document: TextDocumentIdentifier): LanguageService | undefined {
218234
return this.getServiceInfo(document).service;
219235
}

server/src/editorServices.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ export class LSHost implements ts.LanguageServiceHost {
781781
positionToLineOffset(filename: string, position: number, lineIndex?: LineIndex): ILineInfo {
782782
lineIndex = lineIndex || this.getLineIndex(filename);
783783
const lineOffset = lineIndex.charOffsetToLineNumberAndPos(position);
784-
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
784+
return { line: lineOffset.line, offset: lineOffset.offset + 1, text: lineOffset.text };
785785
}
786786

787787
getLineIndex(filename: string): LineIndex {
@@ -1699,6 +1699,15 @@ export class ProjectService {
16991699
}
17001700
}
17011701

1702+
positionToLineOffset(fileName: string, offset: number) {
1703+
const file = normalizePath(fileName);
1704+
const project = this.getProjectForFile(file);
1705+
if (project && !project.languageServiceDiabled) {
1706+
const compilerService = project.compilerService;
1707+
return compilerService.host.positionToLineOffset(fileName, offset);
1708+
}
1709+
}
1710+
17021711
private requestUpdateProjectStructure(changeSeq: number, matchSeq: (changeSeq: number) => boolean) {
17031712
this.host.setTimeout(() => {
17041713
if (matchSeq(changeSeq)) {
@@ -3041,7 +3050,7 @@ function logServiceTimes(logger: Logger, service: ng.LanguageService): ng.Langua
30413050
return time("getCompletions", () => service.getCompletionsAt(fileName, position));
30423051
},
30433052
getDiagnostics(fileName) {
3044-
return time("getDiagnnostics", () => service.getDiagnostics(fileName));
3053+
return time("getDiagnostics", () => service.getDiagnostics(fileName));
30453054
},
30463055
getTemplateReferences() {
30473056
return time("getTemplateRefrences", () => service.getTemplateReferences());

server/src/server.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import {
1313
createConnection, IConnection, TextDocumentSyncKind,
1414
TextDocument, Diagnostic, DiagnosticSeverity,
1515
InitializeParams, InitializeResult, TextDocumentPositionParams,
16-
CompletionItem, CompletionItemKind, TextDocumentIdentifier, Range
16+
CompletionItem, CompletionItemKind, Definition, TextDocumentIdentifier,
17+
Position, Range, TextEdit
1718
} from 'vscode-languageserver';
1819

1920
import {TextDocuments, TextDocumentEvent} from './documents';
2021
import {ErrorCollector} from './errors';
2122

23+
import {Completion, Span} from '@angular/language-service';
24+
2225
// Create a connection for the server. The connection uses Node's IPC as a transport
2326
let connection: IConnection = createConnection();
2427

@@ -80,6 +83,45 @@ function compiletionKindToCompletionItemKind(kind: string): number {
8083
return CompletionItemKind.Text;
8184
}
8285

86+
const wordRe = /(\w|\(|\)|\[|\]|\*|\-|\_|\.)+/g;
87+
const special = /\(|\)|\[|\]|\*|\-|\_|\./;
88+
89+
// Convert attribute names with non-\w chracters into a text edit.
90+
function insertionToEdit(range: Range, insertText: string): TextEdit {
91+
if (insertText.match(special) && range) {
92+
return TextEdit.replace(range, insertText);
93+
}
94+
}
95+
96+
97+
function getReplaceRange(document: TextDocumentIdentifier, offset: number): Range {
98+
const line = documents.getDocumentLine(document, offset);
99+
if (line && line.text && line.start <= offset && line.start + line.text.length >= offset) {
100+
const lineOffset = offset - line.start - 1;
101+
102+
// Find the word that contains the offset
103+
let found: number, len: number;
104+
line.text.replace(wordRe, <any>((word: string, _: string, wordOffset: number) => {
105+
if (wordOffset <= lineOffset && wordOffset + word.length >= lineOffset && word.match(special)) {
106+
found = wordOffset;
107+
len = word.length;
108+
}
109+
}));
110+
if (found != null) {
111+
return Range.create(Position.create(line.line - 1, found), Position.create(line.line - 1, found + len));
112+
}
113+
}
114+
}
115+
116+
function insertTextOf(completion: Completion): string {
117+
switch (completion.kind) {
118+
case 'attribute':
119+
case 'html attribute':
120+
return `${completion.name}="{{}}"`
121+
}
122+
return completion.name;
123+
}
124+
83125
// This handler provides the initial list of the completion items.
84126
connection.onCompletion((textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
85127
const {fileName, service, offset, languageId} = documents.getServiceInfo(textDocumentPosition.textDocument,
@@ -91,16 +133,18 @@ connection.onCompletion((textDocumentPosition: TextDocumentPositionParams): Comp
91133
result = result.filter(completion => completion.kind != 'element');
92134
}
93135
if (result) {
136+
const replaceRange = getReplaceRange(textDocumentPosition.textDocument, offset);
94137
return result.map(completion => ({
95138
label: completion.name,
96139
kind: compiletionKindToCompletionItemKind(completion.kind),
97140
detail: completion.kind,
98-
sortText: completion.sort
141+
sortText: completion.sort,
142+
textEdit: insertionToEdit(replaceRange, insertTextOf(completion)),
143+
insertText: insertTextOf(completion)
99144
}));
100145
}
101146
}
102147
});
103148

104-
105149
// Listen on the connection
106150
connection.listen();

0 commit comments

Comments
 (0)