1/* -------------------------------------------------------------------------------------------- 2 * Copyright (c) Microsoft Corporation. All rights reserved. 3 * Licensed under the MIT License. See License.txt in the project root for license information. 4 * ------------------------------------------------------------------------------------------ */ 5import { 6 createConnection, 7 TextDocuments, 8 Diagnostic, 9 DiagnosticSeverity, 10 ProposedFeatures, 11 InitializeParams, 12 DidChangeConfigurationNotification, 13 CompletionItem, 14 CompletionItemKind, 15 TextDocumentPositionParams, 16 TextDocumentSyncKind, 17 InitializeResult, 18 IConnection 19} from 'vscode-languageserver'; 20 21import { 22 TextDocument 23} from 'vscode-languageserver-textdocument'; 24 25import { 26 createProtocolConnection, createClientSocketTransport 27} from 'vscode-languageserver-protocol'; 28 29function RunServer(connection: IConnection) { 30 // Create a simple text document manager. 31 let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); 32 33 let hasConfigurationCapability: boolean = false; 34 let hasWorkspaceFolderCapability: boolean = false; 35 let hasDiagnosticRelatedInformationCapability: boolean = false; 36 37 connection.onInitialize((params: InitializeParams) => { 38 let capabilities = params.capabilities; 39 40 // Does the client support the `workspace/configuration` request? 41 // If not, we fall back using global settings. 42 hasConfigurationCapability = !!( 43 capabilities.workspace && !!capabilities.workspace.configuration 44 ); 45 hasWorkspaceFolderCapability = !!( 46 capabilities.workspace && !!capabilities.workspace.workspaceFolders 47 ); 48 hasDiagnosticRelatedInformationCapability = !!( 49 capabilities.textDocument && 50 capabilities.textDocument.publishDiagnostics && 51 capabilities.textDocument.publishDiagnostics.relatedInformation 52 ); 53 54 const result: InitializeResult = { 55 capabilities: { 56 textDocumentSync: TextDocumentSyncKind.Incremental, 57 // Tell the client that this server supports code completion. 58 completionProvider: { 59 resolveProvider: true 60 } 61 } 62 }; 63 if (hasWorkspaceFolderCapability) { 64 result.capabilities.workspace = { 65 workspaceFolders: { 66 supported: true 67 } 68 }; 69 } 70 return result; 71 }); 72 73 connection.onInitialized(() => { 74 if (hasConfigurationCapability) { 75 // Register for all configuration changes. 76 connection.client.register(DidChangeConfigurationNotification.type, undefined); 77 } 78 if (hasWorkspaceFolderCapability) { 79 connection.workspace.onDidChangeWorkspaceFolders(_event => { 80 connection.console.log('Workspace folder change event received.'); 81 }); 82 } 83 }); 84 85 // The example settings 86 interface ExampleSettings { 87 maxNumberOfProblems: number; 88 } 89 90 // The global settings, used when the `workspace/configuration` request is not supported by the client. 91 // Please note that this is not the case when using this server with the client provided in this example 92 // but could happen with other clients. 93 const defaultSettings: ExampleSettings = { maxNumberOfProblems: 1000 }; 94 let globalSettings: ExampleSettings = defaultSettings; 95 96 // Cache the settings of all open documents 97 let documentSettings: Map<string, Thenable<ExampleSettings>> = new Map(); 98 99 connection.onDidChangeConfiguration(change => { 100 if (hasConfigurationCapability) { 101 // Reset all cached document settings 102 documentSettings.clear(); 103 } else { 104 globalSettings = <ExampleSettings>( 105 (change.settings.languageServerExample || defaultSettings) 106 ); 107 } 108 109 // Revalidate all open text documents 110 documents.all().forEach(validateTextDocument); 111 }); 112 113 function getDocumentSettings(resource: string): Thenable<ExampleSettings> { 114 if (!hasConfigurationCapability) { 115 return Promise.resolve(globalSettings); 116 } 117 let result = documentSettings.get(resource); 118 if (!result) { 119 result = connection.workspace.getConfiguration({ 120 scopeUri: resource, 121 section: 'languageServerExample' 122 }); 123 documentSettings.set(resource, result); 124 } 125 return result; 126 } 127 128 // Only keep settings for open documents 129 documents.onDidClose(e => { 130 documentSettings.delete(e.document.uri); 131 }); 132 133 // The content of a text document has changed. This event is emitted 134 // when the text document first opened or when its content has changed. 135 documents.onDidChangeContent(change => { 136 validateTextDocument(change.document); 137 }); 138 139 async function validateTextDocument(textDocument: TextDocument): Promise<void> { 140 // In this simple example we get the settings for every validate run. 141 let settings = await getDocumentSettings(textDocument.uri); 142 143 // The validator creates diagnostics for all uppercase words length 2 and more 144 let text = textDocument.getText(); 145 let pattern = /\b[A-Z]{2,}\b/g; 146 let m: RegExpExecArray | null; 147 148 let problems = 0; 149 let diagnostics: Diagnostic[] = []; 150 while ((m = pattern.exec(text)) && problems < settings.maxNumberOfProblems) { 151 problems++; 152 let diagnostic: Diagnostic = { 153 severity: DiagnosticSeverity.Warning, 154 range: { 155 start: textDocument.positionAt(m.index), 156 end: textDocument.positionAt(m.index + m[0].length) 157 }, 158 message: `${m[0]} is all uppercase.`, 159 source: 'ex' 160 }; 161 if (hasDiagnosticRelatedInformationCapability) { 162 diagnostic.relatedInformation = [ 163 { 164 location: { 165 uri: textDocument.uri, 166 range: Object.assign({}, diagnostic.range) 167 }, 168 message: 'Spelling matters' 169 }, 170 { 171 location: { 172 uri: textDocument.uri, 173 range: Object.assign({}, diagnostic.range) 174 }, 175 message: 'Particularly for names' 176 } 177 ]; 178 } 179 diagnostics.push(diagnostic); 180 } 181 182 // Send the computed diagnostics to VSCode. 183 connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); 184 } 185 186 connection.onDidChangeWatchedFiles(_change => { 187 // Monitored files have change in VSCode 188 connection.console.log('We received an file change event'); 189 }); 190 191 // This handler provides the initial list of the completion items. 192 connection.onCompletion( 193 (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { 194 // The pass parameter contains the position of the text document in 195 // which code complete got requested. For the example we ignore this 196 // info and always provide the same completion items. 197 return [ 198 { 199 label: 'TypeScript', 200 kind: CompletionItemKind.Text, 201 data: 1 202 }, 203 { 204 label: 'JavaScript', 205 kind: CompletionItemKind.Text, 206 data: 2 207 } 208 ]; 209 } 210 ); 211 212 // This handler resolves additional information for the item selected in 213 // the completion list. 214 connection.onCompletionResolve( 215 (item: CompletionItem): CompletionItem => { 216 if (item.data === 1) { 217 item.detail = 'TypeScript details'; 218 item.documentation = 'TypeScript documentation'; 219 } else if (item.data === 2) { 220 item.detail = 'JavaScript details'; 221 item.documentation = 'JavaScript documentation'; 222 } 223 return item; 224 } 225 ); 226 227 // Make the text document manager listen on the connection 228 // for open, change and close text document events 229 documents.listen(connection); 230 231 // Listen on the connection 232 connection.listen(); 233} 234 235// Create a connection for the server, using Node's IPC as a transport. 236// Also include all preview / proposed LSP features. 237const idx = process.argv.indexOf('--listen') 238if (idx >= 0 && idx + 1 < process.argv.length) { 239 const port = parseInt(process.argv[idx + 1]); 240 createClientSocketTransport(port).then((transport) => { 241 return transport.onConnected().then((protocol) => { 242 let connection = createConnection(protocol[0], protocol[1]) 243 RunServer(connection); 244 }); 245 }); 246 247} else { 248 let connection = createConnection(ProposedFeatures.all); 249 RunServer(connection); 250} 251