@@ -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
1920import { TextDocuments , TextDocumentEvent } from './documents' ;
2021import { 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
2326let 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.
84126connection . 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
106150connection . listen ( ) ;
0 commit comments