├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── protocol ├── README.md └── mod.ts ├── textdocument ├── README.md ├── mod.ts └── test │ ├── edits_test.ts │ ├── helper.ts │ └── textdocument_test.ts └── types ├── README.md ├── mod.ts └── test ├── edits_test.ts ├── textdocument_test.ts └── typeguards_test.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | * eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: ${{ matrix.kind }} ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [macOS-latest, ubuntu-latest, windows-latest] 12 | 13 | env: 14 | GH_ACTIONS: true 15 | DENO_BUILD_MODE: release 16 | V8_BINARY: true 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Setup Deno 21 | uses: denolib/setup-deno@master 22 | with: 23 | deno-version: 0.41.0 24 | 25 | - name: Cache Deno Generated Files 26 | uses: actions/cache@v1 27 | with: 28 | path: ~/.cache/deno 29 | key: ${{ runner.os }}-deno-cache-${{ hashFiles('**/mod.ts') }} 30 | 31 | - name: Format 32 | run: deno fmt --check 33 | 34 | - name: Tests 35 | run: deno test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .cache 3 | .DS_Store 4 | *bak 5 | .history 6 | .temp/** 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["justjavac.vscode-deno"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## x.y.z - [yyy-mm-dd] 4 | 5 | - xxxx 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) justjavac. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VSCode Language Server - Deno 2 | 3 | [![tag](https://img.shields.io/github/release/denodev/deno_vscode_languageserver)](https://github.com/denodev/deno_vscode_languageserver/releases) 4 | [![Build Status](https://github.com/denodev/deno_vscode_languageserver/workflows/ci/badge.svg?branch=master)](https://github.com/denodev/deno_vscode_languageserver/actions) 5 | [![license](https://img.shields.io/github/license/denodev/deno_vscode_languageserver)](https://github.com/denodev/deno_vscode_languageserver/blob/master/LICENSE) 6 | [![](https://img.shields.io/badge/deno-v0.41.0-green.svg)](https://github.com/denoland/deno) 7 | 8 | Language server protocol implementation for VSCode. This allows implementing language services in JS/TS running on Deno. 9 | 10 | This repository contains the code for the following Deno modules: 11 | 12 | - [ ] vscode_languageserver: implement a VSCode language server using Deno as a runtime. 13 | - [x] vscode_languageserver_textdocument: implement text documents usable in a LSP server using Deno as a runtime. 14 | - [ ] vscode_languageserver_protocol: the actual language server protocol definition in TypeScript. 15 | - [x] vscode_languageserver_types: data types used by the language server client and server. 16 | - [ ] vscode_jsonrpc: the underlying message protocol to communicate between a client and a server. 17 | 18 | **NOTE**: vscode-languageclient: npm module to talk to a VSCode language server from a VSCode extension. _Maybe we don't need to implement it in Deno_. 19 | 20 | ### License 21 | 22 | [deno_vscode_languageserver](https://github.com/denodev/deno_vscode_languageserver) is released under the MIT License. See the bundled [LICENSE](./LICENSE) file for details. 23 | -------------------------------------------------------------------------------- /protocol/README.md: -------------------------------------------------------------------------------- 1 | # VSCode Language Server - Protocol for Deno (WIP) 2 | 3 | [![tag](https://img.shields.io/github/release/denodev/deno_vscode_languageserver)](https://github.com/denodev/deno_vscode_languageserver/releases) 4 | [![Build Status](https://github.com/denodev/deno_vscode_languageserver/workflows/ci/badge.svg?branch=master)](https://github.com/denodev/deno_vscode_languageserver/actions) 5 | 6 | Deno module is a tool independent implementation of the language server protocol and can be used in any type of Deno application. 7 | 8 | **Modified from [microsoft/vscode-languageserver-node's Protocol @312a292](https://github.com/microsoft/vscode-languageserver-node/tree/312a292c17be6055e8beea32c01cd7c6780ffd3b/protocol)**. 9 | 10 | --------- 11 | 12 | > See [here](https://github.com/Microsoft/language-server-protocol) for a detailed documentation on the language server protocol. 13 | > 14 | > ## History 15 | > 16 | > For the history please see the [main repository](https://github.com/Microsoft/vscode-languageserver-node/blob/master/README.md) 17 | > 18 | > ## License 19 | > [MIT](https://github.com/Microsoft/vscode-languageserver-node/blob/master/License.txt) -------------------------------------------------------------------------------- /protocol/mod.ts: -------------------------------------------------------------------------------- 1 | console.log("🦕Need Help"); 2 | -------------------------------------------------------------------------------- /textdocument/README.md: -------------------------------------------------------------------------------- 1 | # Text Document implementation for a LSP Deno server 2 | 3 | [![tag](https://img.shields.io/github/release/denodev/deno_vscode_languageserver)](https://github.com/denodev/deno_vscode_languageserver/releases) 4 | [![Build Status](https://github.com/denodev/deno_vscode_languageserver/workflows/ci/badge.svg?branch=master)](https://github.com/denodev/deno_vscode_languageserver/actions) 5 | 6 | Deno module containing a simple text document implementation for [Deno](https://deno.land) language server. 7 | 8 | **Modified from [microsoft/vscode-languageserver-node's textDocument @f81b7da](https://github.com/microsoft/vscode-languageserver-node/blob/f81b7dade216829869e5cc03e594660ddd4a810a/textDocument/src/main.ts)**. 9 | 10 | -------------- 11 | 12 | > Click [here](https://code.visualstudio.com/docs/extensions/example-language-server) for a detailed document on how 13 | > to implement language servers for [VSCode](https://code.visualstudio.com/). 14 | > 15 | > ## History 16 | > 17 | > ### 1.0.0 18 | > 19 | > Initial version. 20 | > 21 | > ## License 22 | > [MIT](https://github.com/Microsoft/vscode-languageserver-node/blob/master/License.txt) 23 | -------------------------------------------------------------------------------- /textdocument/mod.ts: -------------------------------------------------------------------------------- 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 | * ------------------------------------------------------------------------------------------ */ 5 | "use strict"; 6 | 7 | /** 8 | * A tagging type for string properties that are actually URIs. 9 | */ 10 | export type DocumentUri = string; 11 | 12 | /** 13 | * Position in a text document expressed as zero-based line and character offset. 14 | * The offsets are based on a UTF-16 string representation. So a string of the form 15 | * `a𐐀b` the character offset of the character `a` is 0, the character offset of `𐐀` 16 | * is 1 and the character offset of b is 3 since `𐐀` is represented using two code 17 | * units in UTF-16. 18 | * 19 | * Positions are line end character agnostic. So you can not specify a position that 20 | * denotes `\r|\n` or `\n|` where `|` represents the character offset. 21 | */ 22 | export interface Position { 23 | /** 24 | * Line position in a document (zero-based). 25 | * If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document. 26 | * If a line number is negative, it defaults to 0. 27 | */ 28 | line: number; 29 | 30 | /** 31 | * Character offset on a line in a document (zero-based). Assuming that the line is 32 | * represented as a string, the `character` value represents the gap between the 33 | * `character` and `character + 1`. 34 | * 35 | * If the character value is greater than the line length it defaults back to the 36 | * line length. 37 | * If a line number is negative, it defaults to 0. 38 | */ 39 | character: number; 40 | } 41 | 42 | /** 43 | * A range in a text document expressed as (zero-based) start and end positions. 44 | * 45 | * If you want to specify a range that contains a line including the line ending 46 | * character(s) then use an end position denoting the start of the next line. 47 | * For example: 48 | * ```ts 49 | * { 50 | * start: { line: 5, character: 23 } 51 | * end : { line 6, character : 0 } 52 | * } 53 | * ``` 54 | */ 55 | export interface Range { 56 | /** 57 | * The range's start position 58 | */ 59 | start: Position; 60 | 61 | /** 62 | * The range's end position. 63 | */ 64 | end: Position; 65 | } 66 | 67 | /** 68 | * A text edit applicable to a text document. 69 | */ 70 | export interface TextEdit { 71 | /** 72 | * The range of the text document to be manipulated. To insert 73 | * text into a document create a range where start === end. 74 | */ 75 | range: Range; 76 | 77 | /** 78 | * The string to be inserted. For delete operations use an 79 | * empty string. 80 | */ 81 | newText: string; 82 | } 83 | 84 | /** 85 | * An event describing a change to a text document. If range and rangeLength are omitted 86 | * the new text is considered to be the full content of the document. 87 | */ 88 | export type TextDocumentContentChangeEvent = { 89 | /** 90 | * The range of the document that changed. 91 | */ 92 | range: Range; 93 | 94 | /** 95 | * The optional length of the range that got replaced. 96 | * 97 | * @deprecated use range instead. 98 | */ 99 | rangeLength?: number; 100 | 101 | /** 102 | * The new text for the provided range. 103 | */ 104 | text: string; 105 | } | { 106 | /** 107 | * The new text of the whole document. 108 | */ 109 | text: string; 110 | }; 111 | 112 | /** 113 | * A simple text document. Not to be implemented. The document keeps the content 114 | * as string. 115 | */ 116 | export interface TextDocument { 117 | /** 118 | * The associated URI for this document. Most documents have the __file__-scheme, indicating that they 119 | * represent files on disk. However, some documents may have other schemes indicating that they are not 120 | * available on disk. 121 | * 122 | * @readonly 123 | */ 124 | readonly uri: DocumentUri; 125 | 126 | /** 127 | * The identifier of the language associated with this document. 128 | * 129 | * @readonly 130 | */ 131 | readonly languageId: string; 132 | 133 | /** 134 | * The version number of this document (it will increase after each 135 | * change, including undo/redo). 136 | * 137 | * @readonly 138 | */ 139 | readonly version: number; 140 | 141 | /** 142 | * Get the text of this document. A substring can be retrieved by 143 | * providing a range. 144 | * 145 | * @param range (optional) An range within the document to return. 146 | * If no range is passed, the full content is returned. 147 | * Invalid range positions are adjusted as described in [Position.line](#Position.line) 148 | * and [Position.character](#Position.character). 149 | * If the start range position is greater than the end range position, 150 | * then the effect of getText is as if the two positions were swapped. 151 | * @return The text of this document or a substring of the text if a 152 | * range is provided. 153 | */ 154 | getText(range?: Range): string; 155 | 156 | /** 157 | * Converts a zero-based offset to a position. 158 | * 159 | * @param offset A zero-based offset. 160 | * @return A valid [position](#Position). 161 | */ 162 | positionAt(offset: number): Position; 163 | 164 | /** 165 | * Converts the position to a zero-based offset. 166 | * Invalid positions are adjusted as described in [Position.line](#Position.line) 167 | * and [Position.character](#Position.character). 168 | * 169 | * @param position A position. 170 | * @return A valid zero-based offset. 171 | */ 172 | offsetAt(position: Position): number; 173 | 174 | /** 175 | * The number of lines in this document. 176 | * 177 | * @readonly 178 | */ 179 | readonly lineCount: number; 180 | } 181 | 182 | class FullTextDocument implements TextDocument { 183 | private _uri: DocumentUri; 184 | private _languageId: string; 185 | private _version: number; 186 | private _content: string; 187 | private _lineOffsets: number[] | undefined; 188 | 189 | public constructor( 190 | uri: DocumentUri, 191 | languageId: string, 192 | version: number, 193 | content: string, 194 | ) { 195 | this._uri = uri; 196 | this._languageId = languageId; 197 | this._version = version; 198 | this._content = content; 199 | this._lineOffsets = undefined; 200 | } 201 | 202 | public get uri(): string { 203 | return this._uri; 204 | } 205 | 206 | public get languageId(): string { 207 | return this._languageId; 208 | } 209 | 210 | public get version(): number { 211 | return this._version; 212 | } 213 | 214 | public getText(range?: Range): string { 215 | if (range) { 216 | const start = this.offsetAt(range.start); 217 | const end = this.offsetAt(range.end); 218 | return this._content.substring(start, end); 219 | } 220 | return this._content; 221 | } 222 | 223 | public update( 224 | changes: TextDocumentContentChangeEvent[], 225 | version: number, 226 | ): void { 227 | for (let change of changes) { 228 | if (FullTextDocument.isIncremental(change)) { 229 | // makes sure start is before end 230 | const range = getWellformedRange(change.range); 231 | 232 | // update content 233 | const startOffset = this.offsetAt(range.start); 234 | const endOffset = this.offsetAt(range.end); 235 | this._content = this._content.substring(0, startOffset) + change.text + 236 | this._content.substring(endOffset, this._content.length); 237 | 238 | // update the offsets 239 | const startLine = Math.max(range.start.line, 0); 240 | const endLine = Math.max(range.end.line, 0); 241 | let lineOffsets = this._lineOffsets!; 242 | const addedLineOffsets = computeLineOffsets( 243 | change.text, 244 | false, 245 | startOffset, 246 | ); 247 | if (endLine - startLine === addedLineOffsets.length) { 248 | for (let i = 0, len = addedLineOffsets.length; i < len; i++) { 249 | lineOffsets[i + startLine + 1] = addedLineOffsets[i]; 250 | } 251 | } else { 252 | if (addedLineOffsets.length < 10000) { 253 | lineOffsets.splice( 254 | startLine + 1, 255 | endLine - startLine, 256 | ...addedLineOffsets, 257 | ); 258 | } else { // avoid too many arguments for splice 259 | this._lineOffsets = lineOffsets = lineOffsets.slice( 260 | 0, 261 | startLine + 1, 262 | ).concat(addedLineOffsets, lineOffsets.slice(endLine + 1)); 263 | } 264 | } 265 | const diff = change.text.length - (endOffset - startOffset); 266 | if (diff !== 0) { 267 | for ( 268 | let i = startLine + 1 + addedLineOffsets.length, 269 | len = lineOffsets.length; 270 | i < len; 271 | i++ 272 | ) { 273 | lineOffsets[i] = lineOffsets[i] + diff; 274 | } 275 | } 276 | } else if (FullTextDocument.isFull(change)) { 277 | this._content = change.text; 278 | this._lineOffsets = undefined; 279 | } else { 280 | throw new Error("Unknown change event received"); 281 | } 282 | } 283 | this._version = version; 284 | } 285 | 286 | private getLineOffsets(): number[] { 287 | if (this._lineOffsets === undefined) { 288 | this._lineOffsets = computeLineOffsets(this._content, true); 289 | } 290 | return this._lineOffsets; 291 | } 292 | 293 | public positionAt(offset: number): Position { 294 | offset = Math.max(Math.min(offset, this._content.length), 0); 295 | 296 | let lineOffsets = this.getLineOffsets(); 297 | let low = 0, high = lineOffsets.length; 298 | if (high === 0) { 299 | return { line: 0, character: offset }; 300 | } 301 | while (low < high) { 302 | let mid = Math.floor((low + high) / 2); 303 | if (lineOffsets[mid] > offset) { 304 | high = mid; 305 | } else { 306 | low = mid + 1; 307 | } 308 | } 309 | // low is the least x for which the line offset is larger than the current offset 310 | // or array.length if no line offset is larger than the current offset 311 | let line = low - 1; 312 | return { line, character: offset - lineOffsets[line] }; 313 | } 314 | 315 | public offsetAt(position: Position) { 316 | let lineOffsets = this.getLineOffsets(); 317 | if (position.line >= lineOffsets.length) { 318 | return this._content.length; 319 | } else if (position.line < 0) { 320 | return 0; 321 | } 322 | let lineOffset = lineOffsets[position.line]; 323 | let nextLineOffset = (position.line + 1 < lineOffsets.length) 324 | ? lineOffsets[position.line + 1] 325 | : this._content.length; 326 | return Math.max( 327 | Math.min(lineOffset + position.character, nextLineOffset), 328 | lineOffset, 329 | ); 330 | } 331 | 332 | public get lineCount() { 333 | return this.getLineOffsets().length; 334 | } 335 | 336 | private static isIncremental( 337 | event: TextDocumentContentChangeEvent, 338 | ): event is { range: Range; rangeLength?: number; text: string } { 339 | let candidate: { range: Range; rangeLength?: number; text: string } = 340 | event as any; 341 | return candidate !== undefined && candidate !== null && 342 | typeof candidate.text === "string" && candidate.range !== undefined && 343 | (candidate.rangeLength === undefined || 344 | typeof candidate.rangeLength === "number"); 345 | } 346 | 347 | private static isFull( 348 | event: TextDocumentContentChangeEvent, 349 | ): event is { text: string } { 350 | let candidate: { range?: Range; rangeLength?: number; text: string } = 351 | event as any; 352 | return candidate !== undefined && candidate !== null && 353 | typeof candidate.text === "string" && candidate.range === undefined && 354 | candidate.rangeLength === undefined; 355 | } 356 | } 357 | 358 | export namespace TextDocument { 359 | /** 360 | * Creates a new text document. 361 | * 362 | * @param uri The document's uri. 363 | * @param languageId The document's language Id. 364 | * @param version The document's initial version number. 365 | * @param content The document's content. 366 | */ 367 | export function create( 368 | uri: DocumentUri, 369 | languageId: string, 370 | version: number, 371 | content: string, 372 | ): TextDocument { 373 | return new FullTextDocument(uri, languageId, version, content); 374 | } 375 | 376 | /** 377 | * Updates a TextDocument by modifing its content. 378 | * 379 | * @param document the document to update. Only documents created by TextDocument.create are valid inputs. 380 | * @param changes the changes to apply to the document. 381 | * @returns The updated TextDocument. Note: That's the same document instance passed in as first parameter. 382 | * 383 | */ 384 | export function update( 385 | document: TextDocument, 386 | changes: TextDocumentContentChangeEvent[], 387 | version: number, 388 | ): TextDocument { 389 | if (document instanceof FullTextDocument) { 390 | document.update(changes, version); 391 | return document; 392 | } else { 393 | throw new Error( 394 | "TextDocument.update: document must be created by TextDocument.create", 395 | ); 396 | } 397 | } 398 | 399 | export function applyEdits( 400 | document: TextDocument, 401 | edits: TextEdit[], 402 | ): string { 403 | let text = document.getText(); 404 | let sortedEdits = mergeSort(edits.map(getWellformedEdit), (a, b) => { 405 | let diff = a.range.start.line - b.range.start.line; 406 | if (diff === 0) { 407 | return a.range.start.character - b.range.start.character; 408 | } 409 | return diff; 410 | }); 411 | let lastModifiedOffset = 0; 412 | const spans = []; 413 | for (const e of sortedEdits) { 414 | let startOffset = document.offsetAt(e.range.start); 415 | if (startOffset < lastModifiedOffset) { 416 | throw new Error("Overlapping edit"); 417 | } else if (startOffset > lastModifiedOffset) { 418 | spans.push(text.substring(lastModifiedOffset, startOffset)); 419 | } 420 | if (e.newText.length) { 421 | spans.push(e.newText); 422 | } 423 | lastModifiedOffset = document.offsetAt(e.range.end); 424 | } 425 | spans.push(text.substr(lastModifiedOffset)); 426 | return spans.join(""); 427 | } 428 | } 429 | 430 | function mergeSort(data: T[], compare: (a: T, b: T) => number): T[] { 431 | if (data.length <= 1) { 432 | // sorted 433 | return data; 434 | } 435 | const p = (data.length / 2) | 0; 436 | const left = data.slice(0, p); 437 | const right = data.slice(p); 438 | 439 | mergeSort(left, compare); 440 | mergeSort(right, compare); 441 | 442 | let leftIdx = 0; 443 | let rightIdx = 0; 444 | let i = 0; 445 | while (leftIdx < left.length && rightIdx < right.length) { 446 | let ret = compare(left[leftIdx], right[rightIdx]); 447 | if (ret <= 0) { 448 | // smaller_equal -> take left to preserve order 449 | data[i++] = left[leftIdx++]; 450 | } else { 451 | // greater -> take right 452 | data[i++] = right[rightIdx++]; 453 | } 454 | } 455 | while (leftIdx < left.length) { 456 | data[i++] = left[leftIdx++]; 457 | } 458 | while (rightIdx < right.length) { 459 | data[i++] = right[rightIdx++]; 460 | } 461 | return data; 462 | } 463 | 464 | const enum CharCode { 465 | /** 466 | * The `\n` character. 467 | */ 468 | LineFeed = 10, 469 | /** 470 | * The `\r` character. 471 | */ 472 | CarriageReturn = 13, 473 | } 474 | 475 | function computeLineOffsets( 476 | text: string, 477 | isAtLineStart: boolean, 478 | textOffset = 0, 479 | ): number[] { 480 | const result: number[] = isAtLineStart ? [textOffset] : []; 481 | for (let i = 0; i < text.length; i++) { 482 | let ch = text.charCodeAt(i); 483 | if (ch === CharCode.CarriageReturn || ch === CharCode.LineFeed) { 484 | if ( 485 | ch === CharCode.CarriageReturn && i + 1 < text.length && 486 | text.charCodeAt(i + 1) === CharCode.LineFeed 487 | ) { 488 | i++; 489 | } 490 | result.push(textOffset + i + 1); 491 | } 492 | } 493 | return result; 494 | } 495 | 496 | function getWellformedRange(range: Range): Range { 497 | const start = range.start; 498 | const end = range.end; 499 | if ( 500 | start.line > end.line || 501 | (start.line === end.line && start.character > end.character) 502 | ) { 503 | return { start: end, end: start }; 504 | } 505 | return range; 506 | } 507 | 508 | function getWellformedEdit(textEdit: TextEdit): TextEdit { 509 | const range = getWellformedRange(textEdit.range); 510 | if (range !== textEdit.range) { 511 | return { newText: textEdit.newText, range }; 512 | } 513 | return textEdit; 514 | } 515 | -------------------------------------------------------------------------------- /textdocument/test/edits_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertThrows, 4 | } from "https://deno.land/std/testing/asserts.ts"; 5 | 6 | import { TextDocument } from "../mod.ts"; 7 | import { 8 | Positions as Position, 9 | Ranges as Range, 10 | TextEdits as TextEdit, 11 | } from "./helper.ts"; 12 | 13 | const applyEdits = TextDocument.applyEdits; 14 | 15 | Deno.test("Edits - inserts", function (): any { 16 | let input = TextDocument.create( 17 | "foo://bar/f", 18 | "html", 19 | 0, 20 | "012345678901234567890123456789", 21 | ); 22 | assertEquals( 23 | applyEdits(input, [TextEdit.insert(Position.create(0, 0), "Hello")]), 24 | "Hello012345678901234567890123456789", 25 | ); 26 | assertEquals( 27 | applyEdits(input, [TextEdit.insert(Position.create(0, 1), "Hello")]), 28 | "0Hello12345678901234567890123456789", 29 | ); 30 | assertEquals( 31 | applyEdits( 32 | input, 33 | [ 34 | TextEdit.insert(Position.create(0, 1), "Hello"), 35 | TextEdit.insert(Position.create(0, 1), "World"), 36 | ], 37 | ), 38 | "0HelloWorld12345678901234567890123456789", 39 | ); 40 | assertEquals( 41 | applyEdits( 42 | input, 43 | [ 44 | TextEdit.insert(Position.create(0, 2), "One"), 45 | TextEdit.insert(Position.create(0, 1), "Hello"), 46 | TextEdit.insert(Position.create(0, 1), "World"), 47 | TextEdit.insert(Position.create(0, 2), "Two"), 48 | TextEdit.insert(Position.create(0, 2), "Three"), 49 | ], 50 | ), 51 | "0HelloWorld1OneTwoThree2345678901234567890123456789", 52 | ); 53 | }); 54 | 55 | Deno.test("Edits - replace", function (): any { 56 | let input = TextDocument.create( 57 | "foo://bar/f", 58 | "html", 59 | 0, 60 | "012345678901234567890123456789", 61 | ); 62 | assertEquals( 63 | applyEdits(input, [TextEdit.replace(Range.create(0, 3, 0, 6), "Hello")]), 64 | "012Hello678901234567890123456789", 65 | ); 66 | assertEquals( 67 | applyEdits( 68 | input, 69 | [ 70 | TextEdit.replace(Range.create(0, 3, 0, 6), "Hello"), 71 | TextEdit.replace(Range.create(0, 6, 0, 9), "World"), 72 | ], 73 | ), 74 | "012HelloWorld901234567890123456789", 75 | ); 76 | assertEquals( 77 | applyEdits( 78 | input, 79 | [ 80 | TextEdit.replace(Range.create(0, 3, 0, 6), "Hello"), 81 | TextEdit.insert(Position.create(0, 6), "World"), 82 | ], 83 | ), 84 | "012HelloWorld678901234567890123456789", 85 | ); 86 | assertEquals( 87 | applyEdits( 88 | input, 89 | [ 90 | TextEdit.insert(Position.create(0, 6), "World"), 91 | TextEdit.replace(Range.create(0, 3, 0, 6), "Hello"), 92 | ], 93 | ), 94 | "012HelloWorld678901234567890123456789", 95 | ); 96 | assertEquals( 97 | applyEdits( 98 | input, 99 | [ 100 | TextEdit.insert(Position.create(0, 3), "World"), 101 | TextEdit.replace(Range.create(0, 3, 0, 6), "Hello"), 102 | ], 103 | ), 104 | "012WorldHello678901234567890123456789", 105 | ); 106 | }); 107 | 108 | Deno.test("Edits - overlap", function (): any { 109 | let input = TextDocument.create( 110 | "foo://bar/f", 111 | "html", 112 | 0, 113 | "012345678901234567890123456789", 114 | ); 115 | assertThrows(() => 116 | applyEdits( 117 | input, 118 | [ 119 | TextEdit.replace(Range.create(0, 3, 0, 6), "Hello"), 120 | TextEdit.insert(Position.create(0, 3), "World"), 121 | ], 122 | ) 123 | ); 124 | assertThrows(() => 125 | applyEdits( 126 | input, 127 | [ 128 | TextEdit.replace(Range.create(0, 3, 0, 6), "Hello"), 129 | TextEdit.insert(Position.create(0, 4), "World"), 130 | ], 131 | ) 132 | ); 133 | }); 134 | 135 | Deno.test("Edits - multiline", function (): any { 136 | let input = TextDocument.create("foo://bar/f", "html", 0, "0\n1\n2\n3\n4"); 137 | assertEquals( 138 | applyEdits( 139 | input, 140 | [ 141 | TextEdit.replace(Range.create(2, 0, 3, 0), "Hello"), 142 | TextEdit.insert(Position.create(1, 1), "World"), 143 | ], 144 | ), 145 | "0\n1World\nHello3\n4", 146 | ); 147 | }); 148 | -------------------------------------------------------------------------------- /textdocument/test/helper.ts: -------------------------------------------------------------------------------- 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 | *--------------------------------------------------------------------------------------------*/ 5 | "use strict"; 6 | 7 | import { Position, Range, TextEdit, TextDocument } from "../mod.ts"; 8 | 9 | export namespace Positions { 10 | export function create(line: number, character: number): Position { 11 | return { line, character }; 12 | } 13 | 14 | export function afterSubstring( 15 | document: TextDocument, 16 | subText: string, 17 | ): Position { 18 | const index = document.getText().indexOf(subText); 19 | return document.positionAt(index + subText.length); 20 | } 21 | } 22 | 23 | export namespace Ranges { 24 | export function create( 25 | startLine: number, 26 | startCharacter: number, 27 | endLine: number, 28 | endCharacter: number, 29 | ): Range { 30 | return { 31 | start: Positions.create(startLine, startCharacter), 32 | end: Positions.create(endLine, endCharacter), 33 | }; 34 | } 35 | 36 | export function forSubstring(document: TextDocument, subText: string): Range { 37 | const index = document.getText().indexOf(subText); 38 | return { 39 | start: document.positionAt(index), 40 | end: document.positionAt(index + subText.length), 41 | }; 42 | } 43 | 44 | export function afterSubstring( 45 | document: TextDocument, 46 | subText: string, 47 | ): Range { 48 | const pos = Positions.afterSubstring(document, subText); 49 | return { start: pos, end: pos }; 50 | } 51 | } 52 | 53 | export namespace TextEdits { 54 | export function replace(range: Range, newText: string): TextEdit { 55 | return { range, newText }; 56 | } 57 | export function insert(position: Position, newText: string): TextEdit { 58 | return { range: { start: position, end: position }, newText }; 59 | } 60 | export function del(range: Range): TextEdit { 61 | return { range, newText: "" }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /textdocument/test/textdocument_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertThrows, 4 | assertStrictEq, 5 | } from "https://deno.land/std/testing/asserts.ts"; 6 | 7 | import { TextDocument } from "../mod.ts"; 8 | import { 9 | Positions, 10 | Ranges, 11 | } from "./helper.ts"; 12 | 13 | function newDocument(str: string) { 14 | return TextDocument.create("file://foo/bar", "text", 0, str); 15 | } 16 | 17 | Deno.test("Text Document Lines Model Validator - Empty content", () => { 18 | const str = ""; 19 | const document = newDocument(str); 20 | assertEquals(document.lineCount, 1); 21 | assertEquals(document.offsetAt(Positions.create(0, 0)), 0); 22 | assertEquals(document.positionAt(0), Positions.create(0, 0)); 23 | }); 24 | 25 | Deno.test("Text Document Lines Model Validator - Single line", () => { 26 | const str = "Hello World"; 27 | const document = newDocument(str); 28 | assertEquals(document.lineCount, 1); 29 | 30 | for (let i = 0; i < str.length; i++) { 31 | assertEquals(document.offsetAt(Positions.create(0, i)), i); 32 | assertEquals(document.positionAt(i), Positions.create(0, i)); 33 | } 34 | }); 35 | 36 | Deno.test("Text Document Lines Model Validator - Multiple lines", () => { 37 | const str = "ABCDE\nFGHIJ\nKLMNO\n"; 38 | const document = newDocument(str); 39 | assertEquals(document.lineCount, 4); 40 | 41 | for (let i = 0; i < str.length; i++) { 42 | const line = Math.floor(i / 6); 43 | const column = i % 6; 44 | 45 | assertEquals(document.offsetAt(Positions.create(line, column)), i); 46 | assertEquals(document.positionAt(i), Positions.create(line, column)); 47 | } 48 | 49 | assertEquals(document.offsetAt(Positions.create(3, 0)), 18); 50 | assertEquals(document.offsetAt(Positions.create(3, 1)), 18); 51 | assertEquals(document.positionAt(18), Positions.create(3, 0)); 52 | assertEquals(document.positionAt(19), Positions.create(3, 0)); 53 | }); 54 | 55 | Deno.test("Text Document Lines Model Validator - Starts with new-line", () => { 56 | const document = newDocument("\nABCDE"); 57 | assertEquals(document.lineCount, 2); 58 | assertEquals(document.positionAt(0), Positions.create(0, 0)); 59 | assertEquals(document.positionAt(1), Positions.create(1, 0)); 60 | assertEquals(document.positionAt(6), Positions.create(1, 5)); 61 | }); 62 | 63 | Deno.test("Text Document Lines Model Validator - New line characters", () => { 64 | let str = "ABCDE\rFGHIJ"; 65 | assertEquals(newDocument(str).lineCount, 2); 66 | 67 | str = "ABCDE\nFGHIJ"; 68 | assertEquals(newDocument(str).lineCount, 2); 69 | 70 | str = "ABCDE\r\nFGHIJ"; 71 | assertEquals(newDocument(str).lineCount, 2); 72 | 73 | str = "ABCDE\n\nFGHIJ"; 74 | assertEquals(newDocument(str).lineCount, 3); 75 | 76 | str = "ABCDE\r\rFGHIJ"; 77 | assertEquals(newDocument(str).lineCount, 3); 78 | 79 | str = "ABCDE\n\rFGHIJ"; 80 | assertEquals(newDocument(str).lineCount, 3); 81 | }); 82 | 83 | Deno.test("Text Document Lines Model Validator - getText(Range)", () => { 84 | const str = "12345\n12345\n12345"; 85 | const document = newDocument(str); 86 | assertEquals(document.getText(), str); 87 | assertEquals(document.getText(Ranges.create(-1, 0, 0, 5)), "12345"); 88 | assertEquals(document.getText(Ranges.create(0, 0, 0, 5)), "12345"); 89 | assertEquals(document.getText(Ranges.create(0, 4, 1, 1)), "5\n1"); 90 | assertEquals(document.getText(Ranges.create(0, 4, 2, 1)), "5\n12345\n1"); 91 | assertEquals(document.getText(Ranges.create(0, 4, 3, 1)), "5\n12345\n12345"); 92 | assertEquals(document.getText(Ranges.create(0, 0, 3, 5)), str); 93 | }); 94 | 95 | Deno.test("Text Document Lines Model Validator - Invalid inputs", () => { 96 | const str = "Hello World"; 97 | const document = newDocument(str); 98 | 99 | // invalid position 100 | assertEquals(document.offsetAt(Positions.create(0, str.length)), str.length); 101 | assertEquals( 102 | document.offsetAt(Positions.create(0, str.length + 3)), 103 | str.length, 104 | ); 105 | assertEquals(document.offsetAt(Positions.create(2, 3)), str.length); 106 | assertEquals(document.offsetAt(Positions.create(-1, 3)), 0); 107 | assertEquals(document.offsetAt(Positions.create(0, -3)), 0); 108 | assertEquals(document.offsetAt(Positions.create(1, -3)), str.length); 109 | 110 | // invalid offsets 111 | assertEquals(document.positionAt(-1), Positions.create(0, 0)); 112 | assertEquals( 113 | document.positionAt(str.length), 114 | Positions.create(0, str.length), 115 | ); 116 | assertEquals( 117 | document.positionAt(str.length + 3), 118 | Positions.create(0, str.length), 119 | ); 120 | }); 121 | 122 | Deno.test("Text Document Full Updates - One full update", () => { 123 | const document = newDocument("abc123"); 124 | TextDocument.update(document, [{ text: "efg456" }], 1); 125 | assertStrictEq(document.version, 1); 126 | assertStrictEq(document.getText(), "efg456"); 127 | }); 128 | 129 | Deno.test("Text Document Full Updates - Several full content updates", () => { 130 | const document = newDocument("abc123"); 131 | TextDocument.update(document, [{ text: "hello" }, { text: "world" }], 2); 132 | assertStrictEq(document.version, 2); 133 | assertStrictEq(document.getText(), "world"); 134 | }); 135 | 136 | // assumes that only '\n' is used 137 | function assertValidLineNumbers(doc: TextDocument) { 138 | const text = doc.getText(); 139 | let expectedLineNumber = 0; 140 | for (let i = 0; i < text.length; i++) { 141 | assertEquals(doc.positionAt(i).line, expectedLineNumber); 142 | const ch = text[i]; 143 | if (ch === "\n") { 144 | expectedLineNumber++; 145 | } 146 | } 147 | assertEquals(doc.positionAt(text.length).line, expectedLineNumber); 148 | } 149 | 150 | Deno.test("Text Document Incremental Updates - Incrementally removing content", () => { 151 | const document = newDocument( 152 | 'function abc() {\n console.log("hello, world!");\n}', 153 | ); 154 | assertEquals(document.lineCount, 3); 155 | assertValidLineNumbers(document); 156 | TextDocument.update( 157 | document, 158 | [{ text: "", range: Ranges.forSubstring(document, "hello, world!") }], 159 | 1, 160 | ); 161 | assertStrictEq(document.version, 1); 162 | assertStrictEq(document.getText(), 'function abc() {\n console.log("");\n}'); 163 | assertEquals(document.lineCount, 3); 164 | assertValidLineNumbers(document); 165 | }); 166 | 167 | Deno.test("Text Document Incremental Updates - Incrementally removing multi-line content", () => { 168 | const document = newDocument("function abc() {\n foo();\n bar();\n \n}"); 169 | assertEquals(document.lineCount, 5); 170 | assertValidLineNumbers(document); 171 | TextDocument.update( 172 | document, 173 | [{ 174 | text: "", 175 | range: Ranges.forSubstring(document, " foo();\n bar();\n"), 176 | }], 177 | 1, 178 | ); 179 | assertStrictEq(document.version, 1); 180 | assertStrictEq(document.getText(), "function abc() {\n \n}"); 181 | assertEquals(document.lineCount, 3); 182 | assertValidLineNumbers(document); 183 | }); 184 | 185 | Deno.test("Text Document Incremental Updates - Incrementally removing multi-line content 2", () => { 186 | const document = newDocument("function abc() {\n foo();\n bar();\n \n}"); 187 | assertEquals(document.lineCount, 5); 188 | assertValidLineNumbers(document); 189 | TextDocument.update( 190 | document, 191 | [{ text: "", range: Ranges.forSubstring(document, "foo();\n bar();") }], 192 | 1, 193 | ); 194 | assertStrictEq(document.version, 1); 195 | assertStrictEq(document.getText(), "function abc() {\n \n \n}"); 196 | assertEquals(document.lineCount, 4); 197 | assertValidLineNumbers(document); 198 | }); 199 | 200 | Deno.test("Text Document Incremental Updates - Incrementally adding content", () => { 201 | const document = newDocument('function abc() {\n console.log("hello");\n}'); 202 | assertEquals(document.lineCount, 3); 203 | assertValidLineNumbers(document); 204 | TextDocument.update( 205 | document, 206 | [{ text: ", world!", range: Ranges.afterSubstring(document, "hello") }], 207 | 1, 208 | ); 209 | assertStrictEq(document.version, 1); 210 | assertStrictEq( 211 | document.getText(), 212 | 'function abc() {\n console.log("hello, world!");\n}', 213 | ); 214 | assertEquals(document.lineCount, 3); 215 | assertValidLineNumbers(document); 216 | }); 217 | 218 | Deno.test("Text Document Incremental Updates - Incrementally adding multi-line content", () => { 219 | const document = newDocument( 220 | "function abc() {\n while (true) {\n foo();\n };\n}", 221 | ); 222 | assertEquals(document.lineCount, 5); 223 | assertValidLineNumbers(document); 224 | TextDocument.update( 225 | document, 226 | [{ 227 | text: "\n bar();", 228 | range: Ranges.afterSubstring(document, "foo();"), 229 | }], 230 | 1, 231 | ); 232 | assertStrictEq(document.version, 1); 233 | assertStrictEq( 234 | document.getText(), 235 | "function abc() {\n while (true) {\n foo();\n bar();\n };\n}", 236 | ); 237 | assertEquals(document.lineCount, 6); 238 | assertValidLineNumbers(document); 239 | }); 240 | 241 | Deno.test("Text Document Incremental Updates - Incrementally replacing single-line content, more chars", () => { 242 | const document = newDocument( 243 | 'function abc() {\n console.log("hello, world!");\n}', 244 | ); 245 | assertEquals(document.lineCount, 3); 246 | assertValidLineNumbers(document); 247 | TextDocument.update( 248 | document, 249 | [{ 250 | text: "hello, test case!!!", 251 | range: Ranges.forSubstring(document, "hello, world!"), 252 | }], 253 | 1, 254 | ); 255 | assertStrictEq(document.version, 1); 256 | assertStrictEq( 257 | document.getText(), 258 | 'function abc() {\n console.log("hello, test case!!!");\n}', 259 | ); 260 | assertEquals(document.lineCount, 3); 261 | assertValidLineNumbers(document); 262 | }); 263 | 264 | Deno.test("Text Document Incremental Updates - Incrementally replacing single-line content, less chars", () => { 265 | const document = newDocument( 266 | 'function abc() {\n console.log("hello, world!");\n}', 267 | ); 268 | assertEquals(document.lineCount, 3); 269 | assertValidLineNumbers(document); 270 | TextDocument.update( 271 | document, 272 | [{ text: "hey", range: Ranges.forSubstring(document, "hello, world!") }], 273 | 1, 274 | ); 275 | assertStrictEq(document.version, 1); 276 | assertStrictEq( 277 | document.getText(), 278 | 'function abc() {\n console.log("hey");\n}', 279 | ); 280 | assertEquals(document.lineCount, 3); 281 | assertValidLineNumbers(document); 282 | }); 283 | 284 | Deno.test("Text Document Incremental Updates - Incrementally replacing single-line content, same num of chars", () => { 285 | const document = newDocument( 286 | 'function abc() {\n console.log("hello, world!");\n}', 287 | ); 288 | assertEquals(document.lineCount, 3); 289 | assertValidLineNumbers(document); 290 | TextDocument.update( 291 | document, 292 | [{ 293 | text: "world, hello!", 294 | range: Ranges.forSubstring(document, "hello, world!"), 295 | }], 296 | 1, 297 | ); 298 | assertStrictEq(document.version, 1); 299 | assertStrictEq( 300 | document.getText(), 301 | 'function abc() {\n console.log("world, hello!");\n}', 302 | ); 303 | assertEquals(document.lineCount, 3); 304 | assertValidLineNumbers(document); 305 | }); 306 | 307 | Deno.test("Text Document Incremental Updates - Incrementally replacing multi-line content, more lines", () => { 308 | const document = newDocument( 309 | 'function abc() {\n console.log("hello, world!");\n}', 310 | ); 311 | assertEquals(document.lineCount, 3); 312 | assertValidLineNumbers(document); 313 | TextDocument.update( 314 | document, 315 | [{ 316 | text: "\n//hello\nfunction d(){", 317 | range: Ranges.forSubstring(document, "function abc() {"), 318 | }], 319 | 1, 320 | ); 321 | assertStrictEq(document.version, 1); 322 | assertStrictEq( 323 | document.getText(), 324 | '\n//hello\nfunction d(){\n console.log("hello, world!");\n}', 325 | ); 326 | assertEquals(document.lineCount, 5); 327 | assertValidLineNumbers(document); 328 | }); 329 | 330 | Deno.test("Text Document Incremental Updates - Incrementally replacing multi-line content, less lines", () => { 331 | const document = newDocument("a1\nb1\na2\nb2\na3\nb3\na4\nb4\n"); 332 | assertEquals(document.lineCount, 9); 333 | assertValidLineNumbers(document); 334 | TextDocument.update( 335 | document, 336 | [{ 337 | text: "xx\nyy", 338 | range: Ranges.forSubstring(document, "\na3\nb3\na4\nb4\n"), 339 | }], 340 | 1, 341 | ); 342 | assertStrictEq(document.version, 1); 343 | assertStrictEq(document.getText(), "a1\nb1\na2\nb2xx\nyy"); 344 | assertEquals(document.lineCount, 5); 345 | assertValidLineNumbers(document); 346 | }); 347 | 348 | Deno.test("Text Document Incremental Updates - Incrementally replacing multi-line content, same num of lines and chars", () => { 349 | const document = newDocument("a1\nb1\na2\nb2\na3\nb3\na4\nb4\n"); 350 | assertEquals(document.lineCount, 9); 351 | assertValidLineNumbers(document); 352 | TextDocument.update( 353 | document, 354 | [{ 355 | text: "\nxx1\nxx2", 356 | range: Ranges.forSubstring(document, "a2\nb2\na3"), 357 | }], 358 | 1, 359 | ); 360 | assertStrictEq(document.version, 1); 361 | assertStrictEq(document.getText(), "a1\nb1\n\nxx1\nxx2\nb3\na4\nb4\n"); 362 | assertEquals(document.lineCount, 9); 363 | assertValidLineNumbers(document); 364 | }); 365 | 366 | Deno.test("Text Document Incremental Updates - Incrementally replacing multi-line content, same num of lines but diff chars", () => { 367 | const document = newDocument("a1\nb1\na2\nb2\na3\nb3\na4\nb4\n"); 368 | assertEquals(document.lineCount, 9); 369 | assertValidLineNumbers(document); 370 | TextDocument.update( 371 | document, 372 | [{ text: "\ny\n", range: Ranges.forSubstring(document, "a2\nb2\na3") }], 373 | 1, 374 | ); 375 | assertStrictEq(document.version, 1); 376 | assertStrictEq(document.getText(), "a1\nb1\n\ny\n\nb3\na4\nb4\n"); 377 | assertEquals(document.lineCount, 9); 378 | assertValidLineNumbers(document); 379 | }); 380 | 381 | Deno.test("Text Document Incremental Updates - Incrementally replacing multi-line content, huge number of lines", () => { 382 | const document = newDocument("a1\ncc\nb1"); 383 | assertEquals(document.lineCount, 3); 384 | assertValidLineNumbers(document); 385 | const text = new Array(20000).join("\ndd"); // a string with 19999 `\n` 386 | TextDocument.update( 387 | document, 388 | [{ text, range: Ranges.forSubstring(document, "\ncc") }], 389 | 1, 390 | ); 391 | assertStrictEq(document.version, 1); 392 | assertStrictEq(document.getText(), "a1" + text + "\nb1"); 393 | assertEquals(document.lineCount, 20001); 394 | assertValidLineNumbers(document); 395 | }); 396 | 397 | Deno.test("Text Document Incremental Updates - Several incremental content changes", () => { 398 | const document = newDocument( 399 | 'function abc() {\n console.log("hello, world!");\n}', 400 | ); 401 | TextDocument.update(document, [ 402 | { text: "defg", range: Ranges.create(0, 12, 0, 12) }, 403 | { text: "hello, test case!!!", range: Ranges.create(1, 15, 1, 28) }, 404 | { text: "hij", range: Ranges.create(0, 16, 0, 16) }, 405 | ], 1); 406 | assertStrictEq(document.version, 1); 407 | assertStrictEq( 408 | document.getText(), 409 | 'function abcdefghij() {\n console.log("hello, test case!!!");\n}', 410 | ); 411 | assertValidLineNumbers(document); 412 | }); 413 | 414 | Deno.test("Text Document Incremental Updates - Basic append", () => { 415 | let document = newDocument("foooo\nbar\nbaz"); 416 | 417 | assertEquals(document.offsetAt(Positions.create(2, 0)), 10); 418 | 419 | TextDocument.update( 420 | document, 421 | [{ text: " some extra content", range: Ranges.create(1, 3, 1, 3) }], 422 | 1, 423 | ); 424 | assertEquals(document.getText(), "foooo\nbar some extra content\nbaz"); 425 | assertEquals(document.version, 1); 426 | assertEquals(document.offsetAt(Positions.create(2, 0)), 29); 427 | assertValidLineNumbers(document); 428 | }); 429 | 430 | Deno.test("Text Document Incremental Updates - Multi-line append", () => { 431 | let document = newDocument("foooo\nbar\nbaz"); 432 | 433 | assertEquals(document.offsetAt(Positions.create(2, 0)), 10); 434 | 435 | TextDocument.update( 436 | document, 437 | [{ text: " some extra\ncontent", range: Ranges.create(1, 3, 1, 3) }], 438 | 1, 439 | ); 440 | assertEquals(document.getText(), "foooo\nbar some extra\ncontent\nbaz"); 441 | assertEquals(document.version, 1); 442 | assertEquals(document.offsetAt(Positions.create(3, 0)), 29); 443 | assertEquals(document.lineCount, 4); 444 | assertValidLineNumbers(document); 445 | }); 446 | 447 | Deno.test("Text Document Incremental Updates - Basic delete", () => { 448 | let document = newDocument("foooo\nbar\nbaz"); 449 | 450 | assertEquals(document.offsetAt(Positions.create(2, 0)), 10); 451 | 452 | TextDocument.update( 453 | document, 454 | [{ text: "", range: Ranges.create(1, 0, 1, 3) }], 455 | 1, 456 | ); 457 | assertEquals(document.getText(), "foooo\n\nbaz"); 458 | assertEquals(document.version, 1); 459 | assertEquals(document.offsetAt(Positions.create(2, 0)), 7); 460 | assertValidLineNumbers(document); 461 | }); 462 | 463 | Deno.test("Text Document Incremental Updates - Multi-line delete", () => { 464 | let lm = newDocument("foooo\nbar\nbaz"); 465 | 466 | assertEquals(lm.offsetAt(Positions.create(2, 0)), 10); 467 | 468 | TextDocument.update(lm, [{ text: "", range: Ranges.create(0, 5, 1, 3) }], 1); 469 | assertEquals(lm.getText(), "foooo\nbaz"); 470 | assertEquals(lm.version, 1); 471 | assertEquals(lm.offsetAt(Positions.create(1, 0)), 6); 472 | assertValidLineNumbers(lm); 473 | }); 474 | 475 | Deno.test("Text Document Incremental Updates - Single character replace", () => { 476 | let document = newDocument("foooo\nbar\nbaz"); 477 | 478 | assertEquals(document.offsetAt(Positions.create(2, 0)), 10); 479 | 480 | TextDocument.update( 481 | document, 482 | [{ text: "z", range: Ranges.create(1, 2, 1, 3) }], 483 | 2, 484 | ); 485 | assertEquals(document.getText(), "foooo\nbaz\nbaz"); 486 | assertEquals(document.version, 2); 487 | assertEquals(document.offsetAt(Positions.create(2, 0)), 10); 488 | assertValidLineNumbers(document); 489 | }); 490 | 491 | Deno.test("Text Document Incremental Updates - Multi-character replace", () => { 492 | let lm = newDocument("foo\nbar"); 493 | 494 | assertEquals(lm.offsetAt(Positions.create(1, 0)), 4); 495 | 496 | TextDocument.update( 497 | lm, 498 | [{ text: "foobar", range: Ranges.create(1, 0, 1, 3) }], 499 | 1, 500 | ); 501 | assertEquals(lm.getText(), "foo\nfoobar"); 502 | assertEquals(lm.version, 1); 503 | assertEquals(lm.offsetAt(Positions.create(1, 0)), 4); 504 | assertValidLineNumbers(lm); 505 | }); 506 | 507 | Deno.test("Text Document Incremental Updates - Invalid update ranges", () => { 508 | // Before the document starts -> before the document starts 509 | let document = newDocument("foo\nbar"); 510 | TextDocument.update( 511 | document, 512 | [{ text: "abc123", range: Ranges.create(-2, 0, -1, 3) }], 513 | 2, 514 | ); 515 | assertEquals(document.getText(), "abc123foo\nbar"); 516 | assertEquals(document.version, 2); 517 | assertValidLineNumbers(document); 518 | 519 | // Before the document starts -> the middle of document 520 | document = newDocument("foo\nbar"); 521 | TextDocument.update( 522 | document, 523 | [{ text: "foobar", range: Ranges.create(-1, 0, 0, 3) }], 524 | 2, 525 | ); 526 | assertEquals(document.getText(), "foobar\nbar"); 527 | assertEquals(document.version, 2); 528 | assertEquals(document.offsetAt(Positions.create(1, 0)), 7); 529 | assertValidLineNumbers(document); 530 | 531 | // The middle of document -> after the document ends 532 | document = newDocument("foo\nbar"); 533 | TextDocument.update( 534 | document, 535 | [{ text: "foobar", range: Ranges.create(1, 0, 1, 10) }], 536 | 2, 537 | ); 538 | assertEquals(document.getText(), "foo\nfoobar"); 539 | assertEquals(document.version, 2); 540 | assertEquals(document.offsetAt(Positions.create(1, 1000)), 10); 541 | assertValidLineNumbers(document); 542 | 543 | // After the document ends -> after the document ends 544 | document = newDocument("foo\nbar"); 545 | TextDocument.update( 546 | document, 547 | [{ text: "abc123", range: Ranges.create(3, 0, 6, 10) }], 548 | 2, 549 | ); 550 | assertEquals(document.getText(), "foo\nbarabc123"); 551 | assertEquals(document.version, 2); 552 | assertValidLineNumbers(document); 553 | 554 | // Before the document starts -> after the document ends 555 | document = newDocument("foo\nbar"); 556 | TextDocument.update( 557 | document, 558 | [{ text: "entirely new content", range: Ranges.create(-1, 1, 2, 10000) }], 559 | 2, 560 | ); 561 | assertEquals(document.getText(), "entirely new content"); 562 | assertEquals(document.version, 2); 563 | assertEquals(document.lineCount, 1); 564 | assertValidLineNumbers(document); 565 | }); 566 | -------------------------------------------------------------------------------- /types/README.md: -------------------------------------------------------------------------------- 1 | # VSCode Language Server Types 2 | 3 | [![tag](https://img.shields.io/github/release/denodev/deno_vscode_languageserver)](https://github.com/denodev/deno_vscode_languageserver/releases) 4 | [![Build Status](https://github.com/denodev/deno_vscode_languageserver/workflows/ci/badge.svg?branch=master)](https://github.com/denodev/deno_vscode_languageserver/actions) 5 | 6 | Deno module containing the types used by the VSCode language client and [Deno](https://deno.land) language server. 7 | 8 | **Modified from [microsoft/vscode-languageserver-node's types @5120691](https://github.com/microsoft/vscode-languageserver-node/blob/5120691bf187dd608ee65efdadc86d25feb45c41/types/src/main.ts)**. 9 | 10 | -------------- 11 | 12 | > Click [here](https://code.visualstudio.com/docs/extensions/example-language-server) for a detailed document on how 13 | > to implement language servers for [VSCode](https://code.visualstudio.com/). 14 | > 15 | > ## History 16 | > 17 | > For the history please see the [main repository](https://github.com/Microsoft/vscode-languageserver-node/blob/master/README.md) 18 | > 19 | > ## License 20 | > [MIT](https://github.com/Microsoft/vscode-languageserver-node/blob/master/License.txt) 21 | -------------------------------------------------------------------------------- /types/mod.ts: -------------------------------------------------------------------------------- 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 | * ------------------------------------------------------------------------------------------ */ 5 | "use strict"; 6 | 7 | /** 8 | * A tagging type for string properties that are actually document URIs. 9 | */ 10 | export type DocumentUri = string; 11 | 12 | /** 13 | * A tagging type for string properties that are actually URIs 14 | * 15 | * @since 3.16.0 - Proposed state 16 | */ 17 | export type URI = string; 18 | 19 | /** 20 | * Position in a text document expressed as zero-based line and character offset. 21 | * The offsets are based on a UTF-16 string representation. So a string of the form 22 | * `a𐐀b` the character offset of the character `a` is 0, the character offset of `𐐀` 23 | * is 1 and the character offset of b is 3 since `𐐀` is represented using two code 24 | * units in UTF-16. 25 | * 26 | * Positions are line end character agnostic. So you can not specify a position that 27 | * denotes `\r|\n` or `\n|` where `|` represents the character offset. 28 | */ 29 | export interface Position { 30 | /** 31 | * Line position in a document (zero-based). 32 | * If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document. 33 | * If a line number is negative, it defaults to 0. 34 | */ 35 | line: number; 36 | 37 | /** 38 | * Character offset on a line in a document (zero-based). Assuming that the line is 39 | * represented as a string, the `character` value represents the gap between the 40 | * `character` and `character + 1`. 41 | * 42 | * If the character value is greater than the line length it defaults back to the 43 | * line length. 44 | * If a line number is negative, it defaults to 0. 45 | */ 46 | character: number; 47 | } 48 | 49 | /** 50 | * The Position namespace provides helper functions to work with 51 | * [Position](#Position) literals. 52 | */ 53 | export namespace Position { 54 | /** 55 | * Creates a new Position literal from the given line and character. 56 | * @param line The position's line. 57 | * @param character The position's character. 58 | */ 59 | export function create(line: number, character: number): Position { 60 | return { line, character }; 61 | } 62 | /** 63 | * Checks whether the given liternal conforms to the [Position](#Position) interface. 64 | */ 65 | export function is(value: any): value is Position { 66 | let candidate = value as Position; 67 | return Is.objectLiteral(candidate) && Is.number(candidate.line) && 68 | Is.number(candidate.character); 69 | } 70 | } 71 | 72 | /** 73 | * A range in a text document expressed as (zero-based) start and end positions. 74 | * 75 | * If you want to specify a range that contains a line including the line ending 76 | * character(s) then use an end position denoting the start of the next line. 77 | * For example: 78 | * ```ts 79 | * { 80 | * start: { line: 5, character: 23 } 81 | * end : { line 6, character : 0 } 82 | * } 83 | * ``` 84 | */ 85 | export interface Range { 86 | /** 87 | * The range's start position 88 | */ 89 | start: Position; 90 | 91 | /** 92 | * The range's end position. 93 | */ 94 | end: Position; 95 | } 96 | 97 | /** 98 | * The Range namespace provides helper functions to work with 99 | * [Range](#Range) literals. 100 | */ 101 | export namespace Range { 102 | /** 103 | * Create a new Range liternal. 104 | * @param start The range's start position. 105 | * @param end The range's end position. 106 | */ 107 | export function create(start: Position, end: Position): Range; 108 | /** 109 | * Create a new Range liternal. 110 | * @param startLine The start line number. 111 | * @param startCharacter The start character. 112 | * @param endLine The end line number. 113 | * @param endCharacter The end character. 114 | */ 115 | export function create( 116 | startLine: number, 117 | startCharacter: number, 118 | endLine: number, 119 | endCharacter: number, 120 | ): Range; 121 | export function create( 122 | one: Position | number, 123 | two: Position | number, 124 | three?: number, 125 | four?: number, 126 | ): Range { 127 | if ( 128 | Is.number(one) && Is.number(two) && Is.number(three) && Is.number(four) 129 | ) { 130 | return { 131 | start: Position.create(one, two), 132 | end: Position.create(three, four), 133 | }; 134 | } else if (Position.is(one) && Position.is(two)) { 135 | return { start: one, end: two }; 136 | } else { 137 | throw new Error( 138 | `Range#create called with invalid arguments[${one}, ${two}, ${three}, ${four}]`, 139 | ); 140 | } 141 | } 142 | /** 143 | * Checks whether the given literal conforms to the [Range](#Range) interface. 144 | */ 145 | export function is(value: any): value is Range { 146 | let candidate = value as Range; 147 | return Is.objectLiteral(candidate) && Position.is(candidate.start) && 148 | Position.is(candidate.end); 149 | } 150 | } 151 | 152 | /** 153 | * Represents a location inside a resource, such as a line 154 | * inside a text file. 155 | */ 156 | export interface Location { 157 | uri: DocumentUri; 158 | range: Range; 159 | } 160 | 161 | /** 162 | * The Location namespace provides helper functions to work with 163 | * [Location](#Location) literals. 164 | */ 165 | export namespace Location { 166 | /** 167 | * Creates a Location literal. 168 | * @param uri The location's uri. 169 | * @param range The location's range. 170 | */ 171 | export function create(uri: DocumentUri, range: Range): Location { 172 | return { uri, range }; 173 | } 174 | /** 175 | * Checks whether the given literal conforms to the [Location](#Location) interface. 176 | */ 177 | export function is(value: any): value is Location { 178 | let candidate = value as Location; 179 | return Is.defined(candidate) && Range.is(candidate.range) && 180 | (Is.string(candidate.uri) || Is.undefined(candidate.uri)); 181 | } 182 | } 183 | 184 | /** 185 | * Represents the connection of two locations. Provides additional metadata over normal [locations](#Location), 186 | * including an origin range. 187 | */ 188 | export interface LocationLink { 189 | /** 190 | * Span of the origin of this link. 191 | * 192 | * Used as the underlined span for mouse definition hover. Defaults to the word range at 193 | * the definition position. 194 | */ 195 | originSelectionRange?: Range; 196 | 197 | /** 198 | * The target resource identifier of this link. 199 | */ 200 | targetUri: DocumentUri; 201 | 202 | /** 203 | * The full target range of this link. If the target for example is a symbol then target range is the 204 | * range enclosing this symbol not including leading/trailing whitespace but everything else 205 | * like comments. This information is typically used to highlight the range in the editor. 206 | */ 207 | targetRange: Range; 208 | 209 | /** 210 | * The range that should be selected and revealed when this link is being followed, e.g the name of a function. 211 | * Must be contained by the the `targetRange`. See also `DocumentSymbol#range` 212 | */ 213 | targetSelectionRange: Range; 214 | } 215 | 216 | /** 217 | * The LocationLink namespace provides helper functions to work with 218 | * [LocationLink](#LocationLink) literals. 219 | */ 220 | export namespace LocationLink { 221 | /** 222 | * Creates a LocationLink literal. 223 | * @param targetUri The definition's uri. 224 | * @param targetRange The full range of the definition. 225 | * @param targetSelectionRange The span of the symbol definition at the target. 226 | * @param originSelectionRange The span of the symbol being defined in the originating source file. 227 | */ 228 | export function create( 229 | targetUri: DocumentUri, 230 | targetRange: Range, 231 | targetSelectionRange: Range, 232 | originSelectionRange?: Range, 233 | ): LocationLink { 234 | return { 235 | targetUri, 236 | targetRange, 237 | targetSelectionRange, 238 | originSelectionRange, 239 | }; 240 | } 241 | 242 | /** 243 | * Checks whether the given literal conforms to the [LocationLink](#LocationLink) interface. 244 | */ 245 | export function is(value: any): value is LocationLink { 246 | let candidate = value as LocationLink; 247 | return Is.defined(candidate) && Range.is(candidate.targetRange) && 248 | Is.string(candidate.targetUri) && 249 | (Range.is(candidate.targetSelectionRange) || 250 | Is.undefined(candidate.targetSelectionRange)) && 251 | (Range.is(candidate.originSelectionRange) || 252 | Is.undefined(candidate.originSelectionRange)); 253 | } 254 | } 255 | 256 | /** 257 | * Represents a color in RGBA space. 258 | */ 259 | export interface Color { 260 | /** 261 | * The red component of this color in the range [0-1]. 262 | */ 263 | readonly red: number; 264 | 265 | /** 266 | * The green component of this color in the range [0-1]. 267 | */ 268 | readonly green: number; 269 | 270 | /** 271 | * The blue component of this color in the range [0-1]. 272 | */ 273 | readonly blue: number; 274 | 275 | /** 276 | * The alpha component of this color in the range [0-1]. 277 | */ 278 | readonly alpha: number; 279 | } 280 | 281 | /** 282 | * The Color namespace provides helper functions to work with 283 | * [Color](#Color) literals. 284 | */ 285 | export namespace Color { 286 | /** 287 | * Creates a new Color literal. 288 | */ 289 | export function create( 290 | red: number, 291 | green: number, 292 | blue: number, 293 | alpha: number, 294 | ): Color { 295 | return { 296 | red, 297 | green, 298 | blue, 299 | alpha, 300 | }; 301 | } 302 | 303 | /** 304 | * Checks whether the given literal conforms to the [Color](#Color) interface. 305 | */ 306 | export function is(value: any): value is Color { 307 | const candidate = value as Color; 308 | return Is.number(candidate.red) && 309 | Is.number(candidate.green) && 310 | Is.number(candidate.blue) && 311 | Is.number(candidate.alpha); 312 | } 313 | } 314 | 315 | /** 316 | * Represents a color range from a document. 317 | */ 318 | export interface ColorInformation { 319 | /** 320 | * The range in the document where this color appers. 321 | */ 322 | range: Range; 323 | 324 | /** 325 | * The actual color value for this color range. 326 | */ 327 | color: Color; 328 | } 329 | 330 | /** 331 | * The ColorInformation namespace provides helper functions to work with 332 | * [ColorInformation](#ColorInformation) literals. 333 | */ 334 | export namespace ColorInformation { 335 | /** 336 | * Creates a new ColorInformation literal. 337 | */ 338 | export function create(range: Range, color: Color): ColorInformation { 339 | return { 340 | range, 341 | color, 342 | }; 343 | } 344 | 345 | /** 346 | * Checks whether the given literal conforms to the [ColorInformation](#ColorInformation) interface. 347 | */ 348 | export function is(value: any): value is ColorInformation { 349 | const candidate = value as ColorInformation; 350 | return Range.is(candidate.range) && Color.is(candidate.color); 351 | } 352 | } 353 | 354 | export interface ColorPresentation { 355 | /** 356 | * The label of this color presentation. It will be shown on the color 357 | * picker header. By default this is also the text that is inserted when selecting 358 | * this color presentation. 359 | */ 360 | label: string; 361 | /** 362 | * An [edit](#TextEdit) which is applied to a document when selecting 363 | * this presentation for the color. When `falsy` the [label](#ColorPresentation.label) 364 | * is used. 365 | */ 366 | textEdit?: TextEdit; 367 | /** 368 | * An optional array of additional [text edits](#TextEdit) that are applied when 369 | * selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves. 370 | */ 371 | additionalTextEdits?: TextEdit[]; 372 | } 373 | 374 | /** 375 | * The Color namespace provides helper functions to work with 376 | * [ColorPresentation](#ColorPresentation) literals. 377 | */ 378 | export namespace ColorPresentation { 379 | /** 380 | * Creates a new ColorInformation literal. 381 | */ 382 | export function create( 383 | label: string, 384 | textEdit?: TextEdit, 385 | additionalTextEdits?: TextEdit[], 386 | ): ColorPresentation { 387 | return { 388 | label, 389 | textEdit, 390 | additionalTextEdits, 391 | }; 392 | } 393 | 394 | /** 395 | * Checks whether the given literal conforms to the [ColorInformation](#ColorInformation) interface. 396 | */ 397 | export function is(value: any): value is ColorPresentation { 398 | const candidate = value as ColorPresentation; 399 | return Is.string(candidate.label) && 400 | (Is.undefined(candidate.textEdit) || TextEdit.is(candidate)) && 401 | (Is.undefined(candidate.additionalTextEdits) || 402 | Is.typedArray( 403 | candidate.additionalTextEdits, 404 | TextEdit.is, 405 | )); 406 | } 407 | } 408 | 409 | /** 410 | * Enum of known range kinds 411 | */ 412 | export enum FoldingRangeKind { 413 | /** 414 | * Folding range for a comment 415 | */ 416 | Comment = "comment", 417 | /** 418 | * Folding range for a imports or includes 419 | */ 420 | Imports = "imports", 421 | /** 422 | * Folding range for a region (e.g. `#region`) 423 | */ 424 | Region = "region", 425 | } 426 | 427 | /** 428 | * Represents a folding range. 429 | */ 430 | export interface FoldingRange { 431 | /** 432 | * The zero-based line number from where the folded range starts. 433 | */ 434 | startLine: number; 435 | 436 | /** 437 | * The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. 438 | */ 439 | startCharacter?: number; 440 | 441 | /** 442 | * The zero-based line number where the folded range ends. 443 | */ 444 | endLine: number; 445 | 446 | /** 447 | * The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. 448 | */ 449 | endCharacter?: number; 450 | 451 | /** 452 | * Describes the kind of the folding range such as `comment' or 'region'. The kind 453 | * is used to categorize folding ranges and used by commands like 'Fold all comments'. See 454 | * [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds. 455 | */ 456 | kind?: string; 457 | } 458 | 459 | /** 460 | * The folding range namespace provides helper functions to work with 461 | * [FoldingRange](#FoldingRange) literals. 462 | */ 463 | export namespace FoldingRange { 464 | /** 465 | * Creates a new FoldingRange literal. 466 | */ 467 | export function create( 468 | startLine: number, 469 | endLine: number, 470 | startCharacter?: number, 471 | endCharacter?: number, 472 | kind?: string, 473 | ): FoldingRange { 474 | const result: FoldingRange = { 475 | startLine, 476 | endLine, 477 | }; 478 | if (Is.defined(startCharacter)) { 479 | result.startCharacter = startCharacter; 480 | } 481 | if (Is.defined(endCharacter)) { 482 | result.endCharacter = endCharacter; 483 | } 484 | if (Is.defined(kind)) { 485 | result.kind = kind; 486 | } 487 | return result; 488 | } 489 | 490 | /** 491 | * Checks whether the given literal conforms to the [FoldingRange](#FoldingRange) interface. 492 | */ 493 | export function is(value: any): value is FoldingRange { 494 | const candidate = value as FoldingRange; 495 | return Is.number(candidate.startLine) && Is.number(candidate.startLine) && 496 | (Is.undefined(candidate.startCharacter) || 497 | Is.number(candidate.startCharacter)) && 498 | (Is.undefined(candidate.endCharacter) || 499 | Is.number(candidate.endCharacter)) && 500 | (Is.undefined(candidate.kind) || Is.string(candidate.kind)); 501 | } 502 | } 503 | 504 | /** 505 | * Represents a related message and source code location for a diagnostic. This should be 506 | * used to point to code locations that cause or related to a diagnostics, e.g when duplicating 507 | * a symbol in a scope. 508 | */ 509 | export interface DiagnosticRelatedInformation { 510 | /** 511 | * The location of this related diagnostic information. 512 | */ 513 | location: Location; 514 | 515 | /** 516 | * The message of this related diagnostic information. 517 | */ 518 | message: string; 519 | } 520 | 521 | /** 522 | * The DiagnosticRelatedInformation namespace provides helper functions to work with 523 | * [DiagnosticRelatedInformation](#DiagnosticRelatedInformation) literals. 524 | */ 525 | export namespace DiagnosticRelatedInformation { 526 | /** 527 | * Creates a new DiagnosticRelatedInformation literal. 528 | */ 529 | export function create( 530 | location: Location, 531 | message: string, 532 | ): DiagnosticRelatedInformation { 533 | return { 534 | location, 535 | message, 536 | }; 537 | } 538 | 539 | /** 540 | * Checks whether the given literal conforms to the [DiagnosticRelatedInformation](#DiagnosticRelatedInformation) interface. 541 | */ 542 | export function is(value: any): value is DiagnosticRelatedInformation { 543 | let candidate: DiagnosticRelatedInformation = 544 | value as DiagnosticRelatedInformation; 545 | return Is.defined(candidate) && Location.is(candidate.location) && 546 | Is.string(candidate.message); 547 | } 548 | } 549 | 550 | /** 551 | * The diagnostic's severity. 552 | */ 553 | export namespace DiagnosticSeverity { 554 | /** 555 | * Reports an error. 556 | */ 557 | export const Error: 1 = 1; 558 | /** 559 | * Reports a warning. 560 | */ 561 | export const Warning: 2 = 2; 562 | /** 563 | * Reports an information. 564 | */ 565 | export const Information: 3 = 3; 566 | /** 567 | * Reports a hint. 568 | */ 569 | export const Hint: 4 = 4; 570 | } 571 | 572 | export type DiagnosticSeverity = 1 | 2 | 3 | 4; 573 | 574 | /** 575 | * The diagnostic tags. 576 | * 577 | * @since 3.15.0 578 | */ 579 | export namespace DiagnosticTag { 580 | /** 581 | * Unused or unnecessary code. 582 | * 583 | * Clients are allowed to render diagnostics with this tag faded out instead of having 584 | * an error squiggle. 585 | */ 586 | export const Unnecessary: 1 = 1; 587 | 588 | /** 589 | * Deprecated or obsolete code. 590 | * 591 | * Clients are allowed to rendered diagnostics with this tag strike through. 592 | */ 593 | export const Deprecated: 2 = 2; 594 | } 595 | 596 | export type DiagnosticTag = 1 | 2; 597 | 598 | /** 599 | * Structure to capture more complex diagnostic codes. 600 | * 601 | * @since 3.16.0 - Proposed state 602 | */ 603 | export interface DiagnosticCode { 604 | /** 605 | * The actual code 606 | */ 607 | value: string | number; 608 | 609 | /** 610 | * A target URI to open with more information about the diagnostic error. 611 | */ 612 | target: URI; 613 | } 614 | 615 | /** 616 | * The DiagnosticCode namespace provides functions to deal with complex diagnostic codes. 617 | * 618 | * @since 3.16.0 - Proposed state 619 | */ 620 | export namespace DiagnosticCode { 621 | /** 622 | * Checks whether the given liternal conforms to the [DiagnosticCode](#DiagnosticCode) interface. 623 | */ 624 | export function is( 625 | value: string | number | DiagnosticCode | undefined | null, 626 | ): value is DiagnosticCode { 627 | const candidate: DiagnosticCode = value as DiagnosticCode; 628 | return candidate !== undefined && candidate !== null && 629 | (Is.number(candidate.value) || Is.string(candidate.value)) && 630 | Is.string(candidate.target); 631 | } 632 | } 633 | 634 | /** 635 | * Represents a diagnostic, such as a compiler error or warning. Diagnostic objects 636 | * are only valid in the scope of a resource. 637 | */ 638 | export interface Diagnostic { 639 | /** 640 | * The range at which the message applies 641 | */ 642 | range: Range; 643 | 644 | /** 645 | * The diagnostic's severity. Can be omitted. If omitted it is up to the 646 | * client to interpret diagnostics as error, warning, info or hint. 647 | */ 648 | severity?: DiagnosticSeverity; 649 | 650 | /** 651 | * The diagnostic's code, which usually appear in the user interface. 652 | * 653 | * @since 3.16.0 Support for `DiagnosticCode` - Proposed state 654 | */ 655 | code?: number | string | DiagnosticCode; 656 | 657 | /** 658 | * A human-readable string describing the source of this 659 | * diagnostic, e.g. 'typescript' or 'super lint'. It usually 660 | * appears in the user interface. 661 | */ 662 | source?: string; 663 | 664 | /** 665 | * The diagnostic's message. It usually appears in the user interface 666 | */ 667 | message: string; 668 | 669 | /** 670 | * Additional metadata about the diagnostic. 671 | * 672 | * @since 3.15.0 673 | */ 674 | tags?: DiagnosticTag[]; 675 | 676 | /** 677 | * An array of related diagnostic information, e.g. when symbol-names within 678 | * a scope collide all definitions can be marked via this property. 679 | */ 680 | relatedInformation?: DiagnosticRelatedInformation[]; 681 | } 682 | 683 | /** 684 | * The Diagnostic namespace provides helper functions to work with 685 | * [Diagnostic](#Diagnostic) literals. 686 | */ 687 | export namespace Diagnostic { 688 | /** 689 | * Creates a new Diagnostic literal. 690 | */ 691 | export function create( 692 | range: Range, 693 | message: string, 694 | severity?: DiagnosticSeverity, 695 | code?: number | string, 696 | source?: string, 697 | relatedInformation?: DiagnosticRelatedInformation[], 698 | ): Diagnostic { 699 | let result: Diagnostic = { range, message }; 700 | if (Is.defined(severity)) { 701 | result.severity = severity; 702 | } 703 | if (Is.defined(code)) { 704 | result.code = code; 705 | } 706 | if (Is.defined(source)) { 707 | result.source = source; 708 | } 709 | if (Is.defined(relatedInformation)) { 710 | result.relatedInformation = relatedInformation; 711 | } 712 | return result; 713 | } 714 | 715 | /** 716 | * Checks whether the given literal conforms to the [Diagnostic](#Diagnostic) interface. 717 | */ 718 | export function is(value: any): value is Diagnostic { 719 | let candidate = value as Diagnostic; 720 | return Is.defined(candidate) && 721 | Range.is(candidate.range) && 722 | Is.string(candidate.message) && 723 | (Is.number(candidate.severity) || Is.undefined(candidate.severity)) && 724 | (Is.number(candidate.code) || Is.string(candidate.code) || 725 | Is.undefined(candidate.code)) && 726 | (Is.string(candidate.source) || Is.undefined(candidate.source)) && 727 | (Is.undefined(candidate.relatedInformation) || 728 | Is.typedArray( 729 | candidate.relatedInformation, 730 | DiagnosticRelatedInformation.is, 731 | )); 732 | } 733 | } 734 | 735 | /** 736 | * Represents a reference to a command. Provides a title which 737 | * will be used to represent a command in the UI and, optionally, 738 | * an array of arguments which will be passed to the command handler 739 | * function when invoked. 740 | */ 741 | export interface Command { 742 | /** 743 | * Title of the command, like `save`. 744 | */ 745 | title: string; 746 | /** 747 | * The identifier of the actual command handler. 748 | */ 749 | command: string; 750 | /** 751 | * Arguments that the command handler should be 752 | * invoked with. 753 | */ 754 | arguments?: any[]; 755 | } 756 | 757 | /** 758 | * The Command namespace provides helper functions to work with 759 | * [Command](#Command) literals. 760 | */ 761 | export namespace Command { 762 | /** 763 | * Creates a new Command literal. 764 | */ 765 | export function create( 766 | title: string, 767 | command: string, 768 | ...args: any[] 769 | ): Command { 770 | let result: Command = { title, command }; 771 | if (Is.defined(args) && args.length > 0) { 772 | result.arguments = args; 773 | } 774 | return result; 775 | } 776 | /** 777 | * Checks whether the given literal conforms to the [Command](#Command) interface. 778 | */ 779 | export function is(value: any): value is Command { 780 | let candidate = value as Command; 781 | return Is.defined(candidate) && Is.string(candidate.title) && 782 | Is.string(candidate.command); 783 | } 784 | } 785 | 786 | /** 787 | * A text edit applicable to a text document. 788 | */ 789 | export interface TextEdit { 790 | /** 791 | * The range of the text document to be manipulated. To insert 792 | * text into a document create a range where start === end. 793 | */ 794 | range: Range; 795 | 796 | /** 797 | * The string to be inserted. For delete operations use an 798 | * empty string. 799 | */ 800 | newText: string; 801 | } 802 | 803 | /** 804 | * The TextEdit namespace provides helper function to create replace, 805 | * insert and delete edits more easily. 806 | */ 807 | export namespace TextEdit { 808 | /** 809 | * Creates a replace text edit. 810 | * @param range The range of text to be replaced. 811 | * @param newText The new text. 812 | */ 813 | export function replace(range: Range, newText: string): TextEdit { 814 | return { range, newText }; 815 | } 816 | /** 817 | * Creates a insert text edit. 818 | * @param position The position to insert the text at. 819 | * @param newText The text to be inserted. 820 | */ 821 | export function insert(position: Position, newText: string): TextEdit { 822 | return { range: { start: position, end: position }, newText }; 823 | } 824 | /** 825 | * Creates a delete text edit. 826 | * @param range The range of text to be deleted. 827 | */ 828 | export function del(range: Range): TextEdit { 829 | return { range, newText: "" }; 830 | } 831 | 832 | export function is(value: any): value is TextEdit { 833 | const candidate = value as TextEdit; 834 | return Is.objectLiteral(candidate) && 835 | Is.string(candidate.newText) && 836 | Range.is(candidate.range); 837 | } 838 | } 839 | 840 | /** 841 | * Describes textual changes on a text document. A TextDocumentEdit describes all changes 842 | * on a document version Si and after they are applied move the document to version Si+1. 843 | * So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any 844 | * kind of ordering. However the edits must be non overlapping. 845 | */ 846 | export interface TextDocumentEdit { 847 | /** 848 | * The text document to change. 849 | */ 850 | textDocument: VersionedTextDocumentIdentifier; 851 | 852 | /** 853 | * The edits to be applied. 854 | */ 855 | edits: TextEdit[]; 856 | } 857 | 858 | /** 859 | * The TextDocumentEdit namespace provides helper function to create 860 | * an edit that manipulates a text document. 861 | */ 862 | export namespace TextDocumentEdit { 863 | /** 864 | * Creates a new `TextDocumentEdit` 865 | */ 866 | export function create( 867 | textDocument: VersionedTextDocumentIdentifier, 868 | edits: TextEdit[], 869 | ): TextDocumentEdit { 870 | return { textDocument, edits }; 871 | } 872 | 873 | export function is(value: any): value is TextDocumentEdit { 874 | let candidate = value as TextDocumentEdit; 875 | return Is.defined(candidate) && 876 | VersionedTextDocumentIdentifier.is(candidate.textDocument) && 877 | Array.isArray(candidate.edits); 878 | } 879 | } 880 | 881 | interface ResourceOperation { 882 | kind: string; 883 | } 884 | 885 | /** 886 | * Options to create a file. 887 | */ 888 | export interface CreateFileOptions { 889 | /** 890 | * Overwrite existing file. Overwrite wins over `ignoreIfExists` 891 | */ 892 | overwrite?: boolean; 893 | /** 894 | * Ignore if exists. 895 | */ 896 | ignoreIfExists?: boolean; 897 | } 898 | 899 | /** 900 | * Create file operation. 901 | */ 902 | export interface CreateFile extends ResourceOperation { 903 | /** 904 | * A create 905 | */ 906 | kind: "create"; 907 | /** 908 | * The resource to create. 909 | */ 910 | uri: DocumentUri; 911 | /** 912 | * Additional options 913 | */ 914 | options?: CreateFileOptions; 915 | } 916 | 917 | export namespace CreateFile { 918 | export function create( 919 | uri: DocumentUri, 920 | options?: CreateFileOptions, 921 | ): CreateFile { 922 | let result: CreateFile = { 923 | kind: "create", 924 | uri, 925 | }; 926 | if ( 927 | options !== void 0 && 928 | (options.overwrite !== void 0 || options.ignoreIfExists !== void 0) 929 | ) { 930 | result.options = options; 931 | } 932 | return result; 933 | } 934 | 935 | export function is(value: any): value is CreateFile { 936 | let candidate: CreateFile = value; 937 | return candidate && candidate.kind === "create" && 938 | Is.string(candidate.uri) && 939 | ( 940 | candidate.options === void 0 || 941 | ((candidate.options.overwrite === void 0 || 942 | Is.boolean(candidate.options.overwrite)) && 943 | (candidate.options.ignoreIfExists === void 0 || 944 | Is.boolean(candidate.options.ignoreIfExists))) 945 | ); 946 | } 947 | } 948 | 949 | /** 950 | * Rename file options 951 | */ 952 | export interface RenameFileOptions { 953 | /** 954 | * Overwrite target if existing. Overwrite wins over `ignoreIfExists` 955 | */ 956 | overwrite?: boolean; 957 | /** 958 | * Ignores if target exists. 959 | */ 960 | ignoreIfExists?: boolean; 961 | } 962 | 963 | /** 964 | * Rename file operation 965 | */ 966 | export interface RenameFile extends ResourceOperation { 967 | /** 968 | * A rename 969 | */ 970 | kind: "rename"; 971 | /** 972 | * The old (existing) location. 973 | */ 974 | oldUri: DocumentUri; 975 | /** 976 | * The new location. 977 | */ 978 | newUri: DocumentUri; 979 | /** 980 | * Rename options. 981 | */ 982 | options?: RenameFileOptions; 983 | } 984 | 985 | export namespace RenameFile { 986 | export function create( 987 | oldUri: DocumentUri, 988 | newUri: DocumentUri, 989 | options?: RenameFileOptions, 990 | ): RenameFile { 991 | let result: RenameFile = { 992 | kind: "rename", 993 | oldUri, 994 | newUri, 995 | }; 996 | if ( 997 | options !== void 0 && 998 | (options.overwrite !== void 0 || options.ignoreIfExists !== void 0) 999 | ) { 1000 | result.options = options; 1001 | } 1002 | return result; 1003 | } 1004 | 1005 | export function is(value: any): value is RenameFile { 1006 | let candidate: RenameFile = value; 1007 | return candidate && candidate.kind === "rename" && 1008 | Is.string(candidate.oldUri) && Is.string(candidate.newUri) && 1009 | ( 1010 | candidate.options === void 0 || 1011 | ((candidate.options.overwrite === void 0 || 1012 | Is.boolean(candidate.options.overwrite)) && 1013 | (candidate.options.ignoreIfExists === void 0 || 1014 | Is.boolean(candidate.options.ignoreIfExists))) 1015 | ); 1016 | } 1017 | } 1018 | 1019 | /** 1020 | * Delete file options 1021 | */ 1022 | export interface DeleteFileOptions { 1023 | /** 1024 | * Delete the content recursively if a folder is denoted. 1025 | */ 1026 | recursive?: boolean; 1027 | /** 1028 | * Ignore the operation if the file doesn't exist. 1029 | */ 1030 | ignoreIfNotExists?: boolean; 1031 | } 1032 | 1033 | /** 1034 | * Delete file operation 1035 | */ 1036 | export interface DeleteFile extends ResourceOperation { 1037 | /** 1038 | * A delete 1039 | */ 1040 | kind: "delete"; 1041 | /** 1042 | * The file to delete. 1043 | */ 1044 | uri: DocumentUri; 1045 | /** 1046 | * Delete options. 1047 | */ 1048 | options?: DeleteFileOptions; 1049 | } 1050 | 1051 | export namespace DeleteFile { 1052 | export function create( 1053 | uri: DocumentUri, 1054 | options?: DeleteFileOptions, 1055 | ): DeleteFile { 1056 | let result: DeleteFile = { 1057 | kind: "delete", 1058 | uri, 1059 | }; 1060 | if ( 1061 | options !== void 0 && 1062 | (options.recursive !== void 0 || options.ignoreIfNotExists !== void 0) 1063 | ) { 1064 | result.options = options; 1065 | } 1066 | return result; 1067 | } 1068 | 1069 | export function is(value: any): value is DeleteFile { 1070 | let candidate: DeleteFile = value; 1071 | return candidate && candidate.kind === "delete" && 1072 | Is.string(candidate.uri) && 1073 | ( 1074 | candidate.options === void 0 || 1075 | ((candidate.options.recursive === void 0 || 1076 | Is.boolean(candidate.options.recursive)) && 1077 | (candidate.options.ignoreIfNotExists === void 0 || 1078 | Is.boolean(candidate.options.ignoreIfNotExists))) 1079 | ); 1080 | } 1081 | } 1082 | 1083 | /** 1084 | * A workspace edit represents changes to many resources managed in the workspace. The edit 1085 | * should either provide `changes` or `documentChanges`. If documentChanges are present 1086 | * they are preferred over `changes` if the client can handle versioned document edits. 1087 | */ 1088 | export interface WorkspaceEdit { 1089 | /** 1090 | * Holds changes to existing resources. 1091 | */ 1092 | changes?: { [uri: string]: TextEdit[] }; 1093 | 1094 | /** 1095 | * Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes 1096 | * are either an array of `TextDocumentEdit`s to express changes to n different text documents 1097 | * where each text document edit addresses a specific version of a text document. Or it can contain 1098 | * above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations. 1099 | * 1100 | * Whether a client supports versioned document edits is expressed via 1101 | * `workspace.workspaceEdit.documentChanges` client capability. 1102 | * 1103 | * If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then 1104 | * only plain `TextEdit`s using the `changes` property are supported. 1105 | */ 1106 | documentChanges?: (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[]; 1107 | } 1108 | 1109 | export namespace WorkspaceEdit { 1110 | export function is(value: any): value is WorkspaceEdit { 1111 | let candidate: WorkspaceEdit = value; 1112 | return candidate && 1113 | (candidate.changes !== void 0 || candidate.documentChanges !== void 0) && 1114 | (candidate.documentChanges === void 0 || 1115 | candidate.documentChanges.every((change) => { 1116 | if (Is.string((change as ResourceOperation).kind)) { 1117 | return CreateFile.is(change) || RenameFile.is(change) || 1118 | DeleteFile.is(change); 1119 | } else { 1120 | return TextDocumentEdit.is(change); 1121 | } 1122 | })); 1123 | } 1124 | } 1125 | 1126 | /** 1127 | * A change to capture text edits for existing resources. 1128 | */ 1129 | export interface TextEditChange { 1130 | /** 1131 | * Gets all text edits for this change. 1132 | * 1133 | * @return An array of text edits. 1134 | */ 1135 | all(): TextEdit[]; 1136 | 1137 | /** 1138 | * Clears the edits for this change. 1139 | */ 1140 | clear(): void; 1141 | 1142 | /** 1143 | * Adds a text edit. 1144 | * @param edit the text edit to add. 1145 | */ 1146 | add(edit: TextEdit): void; 1147 | 1148 | /** 1149 | * Insert the given text at the given position. 1150 | * 1151 | * @param position A position. 1152 | * @param newText A string. 1153 | */ 1154 | insert(position: Position, newText: string): void; 1155 | 1156 | /** 1157 | * Replace the given range with given text for the given resource. 1158 | * 1159 | * @param range A range. 1160 | * @param newText A string. 1161 | */ 1162 | replace(range: Range, newText: string): void; 1163 | 1164 | /** 1165 | * Delete the text at the given range. 1166 | * 1167 | * @param range A range. 1168 | */ 1169 | delete(range: Range): void; 1170 | } 1171 | 1172 | class TextEditChangeImpl implements TextEditChange { 1173 | private edits: TextEdit[]; 1174 | 1175 | public constructor(edits: TextEdit[]) { 1176 | this.edits = edits; 1177 | } 1178 | 1179 | public insert(position: Position, newText: string): void { 1180 | this.edits.push(TextEdit.insert(position, newText)); 1181 | } 1182 | 1183 | public replace(range: Range, newText: string): void { 1184 | this.edits.push(TextEdit.replace(range, newText)); 1185 | } 1186 | 1187 | public delete(range: Range): void { 1188 | this.edits.push(TextEdit.del(range)); 1189 | } 1190 | 1191 | public add(edit: TextEdit): void { 1192 | this.edits.push(edit); 1193 | } 1194 | 1195 | public all(): TextEdit[] { 1196 | return this.edits; 1197 | } 1198 | 1199 | public clear(): void { 1200 | this.edits.splice(0, this.edits.length); 1201 | } 1202 | } 1203 | 1204 | /** 1205 | * A workspace change helps constructing changes to a workspace. 1206 | */ 1207 | export class WorkspaceChange { 1208 | private _workspaceEdit: WorkspaceEdit | undefined; 1209 | private _textEditChanges: { [uri: string]: TextEditChange }; 1210 | 1211 | constructor(workspaceEdit?: WorkspaceEdit) { 1212 | this._textEditChanges = Object.create(null); 1213 | if (workspaceEdit) { 1214 | this._workspaceEdit = workspaceEdit; 1215 | if (workspaceEdit.documentChanges) { 1216 | workspaceEdit.documentChanges.forEach((change) => { 1217 | if (TextDocumentEdit.is(change)) { 1218 | let textEditChange = new TextEditChangeImpl(change.edits); 1219 | this._textEditChanges[change.textDocument.uri] = textEditChange; 1220 | } 1221 | }); 1222 | } else if (workspaceEdit.changes) { 1223 | Object.keys(workspaceEdit.changes).forEach((key) => { 1224 | let textEditChange = new TextEditChangeImpl( 1225 | workspaceEdit.changes![key], 1226 | ); 1227 | this._textEditChanges[key] = textEditChange; 1228 | }); 1229 | } 1230 | } 1231 | } 1232 | 1233 | /** 1234 | * Returns the underlying [WorkspaceEdit](#WorkspaceEdit) literal 1235 | * use to be returned from a workspace edit operation like rename. 1236 | */ 1237 | public get edit(): WorkspaceEdit | undefined { 1238 | return this._workspaceEdit; 1239 | } 1240 | 1241 | /** 1242 | * Returns the [TextEditChange](#TextEditChange) to manage text edits 1243 | * for resources. 1244 | */ 1245 | public getTextEditChange( 1246 | textDocument: VersionedTextDocumentIdentifier, 1247 | ): TextEditChange; 1248 | public getTextEditChange(uri: DocumentUri): TextEditChange; 1249 | public getTextEditChange( 1250 | key: DocumentUri | VersionedTextDocumentIdentifier, 1251 | ): TextEditChange { 1252 | if (VersionedTextDocumentIdentifier.is(key)) { 1253 | if (!this._workspaceEdit) { 1254 | this._workspaceEdit = { 1255 | documentChanges: [], 1256 | }; 1257 | } 1258 | if (!this._workspaceEdit.documentChanges) { 1259 | throw new Error( 1260 | "Workspace edit is not configured for document changes.", 1261 | ); 1262 | } 1263 | let textDocument: VersionedTextDocumentIdentifier = key; 1264 | let result: TextEditChange = this._textEditChanges[textDocument.uri]; 1265 | if (!result) { 1266 | let edits: TextEdit[] = []; 1267 | let textDocumentEdit: TextDocumentEdit = { 1268 | textDocument, 1269 | edits, 1270 | }; 1271 | this._workspaceEdit.documentChanges.push(textDocumentEdit); 1272 | result = new TextEditChangeImpl(edits); 1273 | this._textEditChanges[textDocument.uri] = result; 1274 | } 1275 | return result; 1276 | } else { 1277 | if (!this._workspaceEdit) { 1278 | this._workspaceEdit = { 1279 | changes: Object.create(null), 1280 | }; 1281 | } 1282 | if (!this._workspaceEdit.changes) { 1283 | throw new Error( 1284 | "Workspace edit is not configured for normal text edit changes.", 1285 | ); 1286 | } 1287 | let result: TextEditChange = this._textEditChanges[key]; 1288 | if (!result) { 1289 | let edits: TextEdit[] = []; 1290 | this._workspaceEdit.changes[key] = edits; 1291 | result = new TextEditChangeImpl(edits); 1292 | this._textEditChanges[key] = result; 1293 | } 1294 | return result; 1295 | } 1296 | } 1297 | 1298 | public createFile(uri: DocumentUri, options?: CreateFileOptions): void { 1299 | this.checkDocumentChanges(); 1300 | this._workspaceEdit!.documentChanges!.push(CreateFile.create(uri, options)); 1301 | } 1302 | 1303 | public renameFile( 1304 | oldUri: DocumentUri, 1305 | newUri: DocumentUri, 1306 | options?: RenameFileOptions, 1307 | ): void { 1308 | this.checkDocumentChanges(); 1309 | this._workspaceEdit!.documentChanges!.push( 1310 | RenameFile.create(oldUri, newUri, options), 1311 | ); 1312 | } 1313 | 1314 | public deleteFile(uri: DocumentUri, options?: DeleteFileOptions): void { 1315 | this.checkDocumentChanges(); 1316 | this._workspaceEdit!.documentChanges!.push(DeleteFile.create(uri, options)); 1317 | } 1318 | 1319 | private checkDocumentChanges() { 1320 | if (!this._workspaceEdit || !this._workspaceEdit.documentChanges) { 1321 | throw new Error("Workspace edit is not configured for document changes."); 1322 | } 1323 | } 1324 | } 1325 | 1326 | /** 1327 | * A literal to identify a text document in the client. 1328 | */ 1329 | export interface TextDocumentIdentifier { 1330 | /** 1331 | * The text document's uri. 1332 | */ 1333 | uri: DocumentUri; 1334 | } 1335 | 1336 | /** 1337 | * The TextDocumentIdentifier namespace provides helper functions to work with 1338 | * [TextDocumentIdentifier](#TextDocumentIdentifier) literals. 1339 | */ 1340 | export namespace TextDocumentIdentifier { 1341 | /** 1342 | * Creates a new TextDocumentIdentifier literal. 1343 | * @param uri The document's uri. 1344 | */ 1345 | export function create(uri: DocumentUri): TextDocumentIdentifier { 1346 | return { uri }; 1347 | } 1348 | /** 1349 | * Checks whether the given literal conforms to the [TextDocumentIdentifier](#TextDocumentIdentifier) interface. 1350 | */ 1351 | export function is(value: any): value is TextDocumentIdentifier { 1352 | let candidate = value as TextDocumentIdentifier; 1353 | return Is.defined(candidate) && Is.string(candidate.uri); 1354 | } 1355 | } 1356 | 1357 | /** 1358 | * An identifier to denote a specific version of a text document. 1359 | */ 1360 | export interface VersionedTextDocumentIdentifier 1361 | extends TextDocumentIdentifier { 1362 | /** 1363 | * The version number of this document. If a versioned text document identifier 1364 | * is sent from the server to the client and the file is not open in the editor 1365 | * (the server has not received an open notification before) the server can send 1366 | * `null` to indicate that the version is unknown and the content on disk is the 1367 | * truth (as speced with document content ownership). 1368 | */ 1369 | version: number | null; 1370 | } 1371 | 1372 | /** 1373 | * The VersionedTextDocumentIdentifier namespace provides helper functions to work with 1374 | * [VersionedTextDocumentIdentifier](#VersionedTextDocumentIdentifier) literals. 1375 | */ 1376 | export namespace VersionedTextDocumentIdentifier { 1377 | /** 1378 | * Creates a new VersionedTextDocumentIdentifier literal. 1379 | * @param uri The document's uri. 1380 | * @param uri The document's text. 1381 | */ 1382 | export function create( 1383 | uri: DocumentUri, 1384 | version: number | null, 1385 | ): VersionedTextDocumentIdentifier { 1386 | return { uri, version }; 1387 | } 1388 | 1389 | /** 1390 | * Checks whether the given literal conforms to the [VersionedTextDocumentIdentifier](#VersionedTextDocumentIdentifier) interface. 1391 | */ 1392 | export function is(value: any): value is VersionedTextDocumentIdentifier { 1393 | let candidate = value as VersionedTextDocumentIdentifier; 1394 | return Is.defined(candidate) && Is.string(candidate.uri) && 1395 | (candidate.version === null || Is.number(candidate.version)); 1396 | } 1397 | } 1398 | 1399 | /** 1400 | * An item to transfer a text document from the client to the 1401 | * server. 1402 | */ 1403 | export interface TextDocumentItem { 1404 | /** 1405 | * The text document's uri. 1406 | */ 1407 | uri: DocumentUri; 1408 | 1409 | /** 1410 | * The text document's language identifier 1411 | */ 1412 | languageId: string; 1413 | 1414 | /** 1415 | * The version number of this document (it will increase after each 1416 | * change, including undo/redo). 1417 | */ 1418 | version: number; 1419 | 1420 | /** 1421 | * The content of the opened text document. 1422 | */ 1423 | text: string; 1424 | } 1425 | 1426 | /** 1427 | * The TextDocumentItem namespace provides helper functions to work with 1428 | * [TextDocumentItem](#TextDocumentItem) literals. 1429 | */ 1430 | export namespace TextDocumentItem { 1431 | /** 1432 | * Creates a new TextDocumentItem literal. 1433 | * @param uri The document's uri. 1434 | * @param languageId The document's language identifier. 1435 | * @param version The document's version number. 1436 | * @param text The document's text. 1437 | */ 1438 | export function create( 1439 | uri: DocumentUri, 1440 | languageId: string, 1441 | version: number, 1442 | text: string, 1443 | ): TextDocumentItem { 1444 | return { uri, languageId, version, text }; 1445 | } 1446 | 1447 | /** 1448 | * Checks whether the given literal conforms to the [TextDocumentItem](#TextDocumentItem) interface. 1449 | */ 1450 | export function is(value: any): value is TextDocumentItem { 1451 | let candidate = value as TextDocumentItem; 1452 | return Is.defined(candidate) && Is.string(candidate.uri) && 1453 | Is.string(candidate.languageId) && Is.number(candidate.version) && 1454 | Is.string(candidate.text); 1455 | } 1456 | } 1457 | 1458 | /** 1459 | * Describes the content type that a client supports in various 1460 | * result literals like `Hover`, `ParameterInfo` or `CompletionItem`. 1461 | * 1462 | * Please note that `MarkupKinds` must not start with a `$`. This kinds 1463 | * are reserved for internal usage. 1464 | */ 1465 | export namespace MarkupKind { 1466 | /** 1467 | * Plain text is supported as a content format 1468 | */ 1469 | export const PlainText: "plaintext" = "plaintext"; 1470 | 1471 | /** 1472 | * Markdown is supported as a content format 1473 | */ 1474 | export const Markdown: "markdown" = "markdown"; 1475 | } 1476 | export type MarkupKind = "plaintext" | "markdown"; 1477 | 1478 | export namespace MarkupKind { 1479 | /** 1480 | * Checks whether the given value is a value of the [MarkupKind](#MarkupKind) type. 1481 | */ 1482 | export function is(value: any): value is MarkupKind { 1483 | const candidate = value as MarkupKind; 1484 | return candidate === MarkupKind.PlainText || 1485 | candidate === MarkupKind.Markdown; 1486 | } 1487 | } 1488 | 1489 | /** 1490 | * A `MarkupContent` literal represents a string value which content is interpreted base on its 1491 | * kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. 1492 | * 1493 | * If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. 1494 | * See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting 1495 | * 1496 | * Here is an example how such a string can be constructed using JavaScript / TypeScript: 1497 | * ```ts 1498 | * let markdown: MarkdownContent = { 1499 | * kind: MarkupKind.Markdown, 1500 | * value: [ 1501 | * '# Header', 1502 | * 'Some text', 1503 | * '```typescript', 1504 | * 'someCode();', 1505 | * '```' 1506 | * ].join('\n') 1507 | * }; 1508 | * ``` 1509 | * 1510 | * *Please Note* that clients might sanitize the return markdown. A client could decide to 1511 | * remove HTML from the markdown to avoid script execution. 1512 | */ 1513 | export interface MarkupContent { 1514 | /** 1515 | * The type of the Markup 1516 | */ 1517 | kind: MarkupKind; 1518 | 1519 | /** 1520 | * The content itself 1521 | */ 1522 | value: string; 1523 | } 1524 | 1525 | export namespace MarkupContent { 1526 | /** 1527 | * Checks whether the given value conforms to the [MarkupContent](#MarkupContent) interface. 1528 | */ 1529 | export function is(value: any): value is MarkupContent { 1530 | const candidate = value as MarkupContent; 1531 | return Is.objectLiteral(value) && MarkupKind.is(candidate.kind) && 1532 | Is.string(candidate.value); 1533 | } 1534 | } 1535 | 1536 | /** 1537 | * The kind of a completion entry. 1538 | */ 1539 | export namespace CompletionItemKind { 1540 | export const Text: 1 = 1; 1541 | export const Method: 2 = 2; 1542 | export const Function: 3 = 3; 1543 | export const Constructor: 4 = 4; 1544 | export const Field: 5 = 5; 1545 | export const Variable: 6 = 6; 1546 | export const Class: 7 = 7; 1547 | export const Interface: 8 = 8; 1548 | export const Module: 9 = 9; 1549 | export const Property: 10 = 10; 1550 | export const Unit: 11 = 11; 1551 | export const Value: 12 = 12; 1552 | export const Enum: 13 = 13; 1553 | export const Keyword: 14 = 14; 1554 | export const Snippet: 15 = 15; 1555 | export const Color: 16 = 16; 1556 | export const File: 17 = 17; 1557 | export const Reference: 18 = 18; 1558 | export const Folder: 19 = 19; 1559 | export const EnumMember: 20 = 20; 1560 | export const Constant: 21 = 21; 1561 | export const Struct: 22 = 22; 1562 | export const Event: 23 = 23; 1563 | export const Operator: 24 = 24; 1564 | export const TypeParameter: 25 = 25; 1565 | } 1566 | 1567 | export type CompletionItemKind = 1568 | | 1 1569 | | 2 1570 | | 3 1571 | | 4 1572 | | 5 1573 | | 6 1574 | | 7 1575 | | 8 1576 | | 9 1577 | | 10 1578 | | 11 1579 | | 12 1580 | | 13 1581 | | 14 1582 | | 15 1583 | | 16 1584 | | 17 1585 | | 18 1586 | | 19 1587 | | 20 1588 | | 21 1589 | | 22 1590 | | 23 1591 | | 24 1592 | | 25; 1593 | 1594 | /** 1595 | * Defines whether the insert text in a completion item should be interpreted as 1596 | * plain text or a snippet. 1597 | */ 1598 | export namespace InsertTextFormat { 1599 | /** 1600 | * The primary text to be inserted is treated as a plain string. 1601 | */ 1602 | export const PlainText: 1 = 1; 1603 | 1604 | /** 1605 | * The primary text to be inserted is treated as a snippet. 1606 | * 1607 | * A snippet can define tab stops and placeholders with `$1`, `$2` 1608 | * and `${3:foo}`. `$0` defines the final tab stop, it defaults to 1609 | * the end of the snippet. Placeholders with equal identifiers are linked, 1610 | * that is typing in one will update others too. 1611 | * 1612 | * See also: https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/common/snippet.md 1613 | */ 1614 | export const Snippet: 2 = 2; 1615 | } 1616 | 1617 | export type InsertTextFormat = 1 | 2; 1618 | 1619 | /** 1620 | * Completion item tags are extra annotations that tweak the rendering of a completion 1621 | * item. 1622 | * 1623 | * @since 3.15.0 1624 | */ 1625 | export namespace CompletionItemTag { 1626 | /** 1627 | * Render a completion as obsolete, usually using a strike-out. 1628 | */ 1629 | export const Deprecated = 1; 1630 | } 1631 | 1632 | export type CompletionItemTag = 1; 1633 | 1634 | /** 1635 | * A special text edit to provide an insert and a replace operation. 1636 | * 1637 | * @since 3.16.0 - Proposed state 1638 | */ 1639 | export interface InsertReplaceEdit { 1640 | /** 1641 | * The string to be inserted. 1642 | */ 1643 | newText: string; 1644 | 1645 | /** 1646 | * The range if the insert is requested 1647 | */ 1648 | insert: Range; 1649 | 1650 | /** 1651 | * The range if the replace is requested. 1652 | */ 1653 | replace: Range; 1654 | } 1655 | 1656 | /** 1657 | * The InsertReplaceEdit namespace provides functions to deal with insert / replace edits. 1658 | * 1659 | * @since 3.16.0 - Proposed state 1660 | */ 1661 | export namespace InsertReplaceEdit { 1662 | /** 1663 | * Creates a new insert / replace edit 1664 | */ 1665 | export function create( 1666 | newText: string, 1667 | insert: Range, 1668 | replace: Range, 1669 | ): InsertReplaceEdit { 1670 | return { newText, insert, replace }; 1671 | } 1672 | 1673 | /** 1674 | * Checks whether the given liternal conforms to the [InsertReplaceEdit](#InsertReplaceEdit) interface. 1675 | */ 1676 | export function is( 1677 | value: TextEdit | InsertReplaceEdit, 1678 | ): value is InsertReplaceEdit { 1679 | const candidate: InsertReplaceEdit = value as InsertReplaceEdit; 1680 | return candidate && Is.string(candidate.newText) && 1681 | Range.is(candidate.insert) && Range.is(candidate.replace); 1682 | } 1683 | } 1684 | 1685 | /** 1686 | * A completion item represents a text snippet that is 1687 | * proposed to complete text that is being typed. 1688 | */ 1689 | export interface CompletionItem { 1690 | /** 1691 | * The label of this completion item. By default 1692 | * also the text that is inserted when selecting 1693 | * this completion. 1694 | */ 1695 | label: string; 1696 | 1697 | /** 1698 | * The kind of this completion item. Based of the kind 1699 | * an icon is chosen by the editor. 1700 | */ 1701 | kind?: CompletionItemKind; 1702 | 1703 | /** 1704 | * Tags for this completion item. 1705 | * 1706 | * @since 3.15.0 1707 | */ 1708 | tags?: CompletionItemTag[]; 1709 | 1710 | /** 1711 | * A human-readable string with additional information 1712 | * about this item, like type or symbol information. 1713 | */ 1714 | detail?: string; 1715 | 1716 | /** 1717 | * A human-readable string that represents a doc-comment. 1718 | */ 1719 | documentation?: string | MarkupContent; 1720 | 1721 | /** 1722 | * Indicates if this item is deprecated. 1723 | * @deprecated Use `tags` instead. 1724 | */ 1725 | deprecated?: boolean; 1726 | 1727 | /** 1728 | * Select this item when showing. 1729 | * 1730 | * *Note* that only one completion item can be selected and that the 1731 | * tool / client decides which item that is. The rule is that the *first* 1732 | * item of those that match best is selected. 1733 | */ 1734 | preselect?: boolean; 1735 | 1736 | /** 1737 | * A string that should be used when comparing this item 1738 | * with other items. When `falsy` the [label](#CompletionItem.label) 1739 | * is used. 1740 | */ 1741 | sortText?: string; 1742 | 1743 | /** 1744 | * A string that should be used when filtering a set of 1745 | * completion items. When `falsy` the [label](#CompletionItem.label) 1746 | * is used. 1747 | */ 1748 | filterText?: string; 1749 | 1750 | /** 1751 | * A string that should be inserted into a document when selecting 1752 | * this completion. When `falsy` the [label](#CompletionItem.label) 1753 | * is used. 1754 | * 1755 | * The `insertText` is subject to interpretation by the client side. 1756 | * Some tools might not take the string literally. For example 1757 | * VS Code when code complete is requested in this example `con` 1758 | * and a completion item with an `insertText` of `console` is provided it 1759 | * will only insert `sole`. Therefore it is recommended to use `textEdit` instead 1760 | * since it avoids additional client side interpretation. 1761 | */ 1762 | insertText?: string; 1763 | 1764 | /** 1765 | * The format of the insert text. The format applies to both the `insertText` property 1766 | * and the `newText` property of a provided `textEdit`. If ommitted defaults to 1767 | * `InsertTextFormat.PlainText`. 1768 | */ 1769 | insertTextFormat?: InsertTextFormat; 1770 | 1771 | /** 1772 | * An [edit](#TextEdit) which is applied to a document when selecting 1773 | * this completion. When an edit is provided the value of 1774 | * [insertText](#CompletionItem.insertText) is ignored. 1775 | * 1776 | * Most editors support two different operation when accepting a completion item. One is to insert a 1777 | * completion text and the other is to replace an existing text with a competion text. Since this can 1778 | * usually not predetermend by a server it can report both ranges. Clients need to signal support for 1779 | * `InsertReplaceEdits` via the `textDocument.completion.insertReplaceSupport` client capability 1780 | * property. 1781 | * 1782 | * *Note 1:* The text edit's range as well as both ranges from a insert replace edit must be a 1783 | * [single line] and they must contain the position at which completion has been requested. 1784 | * *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range must be a prefix of 1785 | * the edit's replace range, that means it must be contained and starting at the same position. 1786 | * 1787 | * @since 3.16.0 additional type `InsertReplaceEdit` - Proposed state 1788 | */ 1789 | textEdit?: TextEdit | InsertReplaceEdit; 1790 | 1791 | /** 1792 | * An optional array of additional [text edits](#TextEdit) that are applied when 1793 | * selecting this completion. Edits must not overlap (including the same insert position) 1794 | * with the main [edit](#CompletionItem.textEdit) nor with themselves. 1795 | * 1796 | * Additional text edits should be used to change text unrelated to the current cursor position 1797 | * (for example adding an import statement at the top of the file if the completion item will 1798 | * insert an unqualified type). 1799 | */ 1800 | additionalTextEdits?: TextEdit[]; 1801 | 1802 | /** 1803 | * An optional set of characters that when pressed while this completion is active will accept it first and 1804 | * then type that character. *Note* that all commit characters should have `length=1` and that superfluous 1805 | * characters will be ignored. 1806 | */ 1807 | commitCharacters?: string[]; 1808 | 1809 | /** 1810 | * An optional [command](#Command) that is executed *after* inserting this completion. *Note* that 1811 | * additional modifications to the current document should be described with the 1812 | * [additionalTextEdits](#CompletionItem.additionalTextEdits)-property. 1813 | */ 1814 | command?: Command; 1815 | 1816 | /** 1817 | * An data entry field that is preserved on a completion item between 1818 | * a [CompletionRequest](#CompletionRequest) and a [CompletionResolveRequest] 1819 | * (#CompletionResolveRequest) 1820 | */ 1821 | data?: any; 1822 | } 1823 | 1824 | /** 1825 | * The CompletionItem namespace provides functions to deal with 1826 | * completion items. 1827 | */ 1828 | export namespace CompletionItem { 1829 | /** 1830 | * Create a completion item and seed it with a label. 1831 | * @param label The completion item's label 1832 | */ 1833 | export function create(label: string): CompletionItem { 1834 | return { label }; 1835 | } 1836 | } 1837 | 1838 | /** 1839 | * Represents a collection of [completion items](#CompletionItem) to be presented 1840 | * in the editor. 1841 | */ 1842 | export interface CompletionList { 1843 | /** 1844 | * This list it not complete. Further typing results in recomputing this list. 1845 | */ 1846 | isIncomplete: boolean; 1847 | 1848 | /** 1849 | * The completion items. 1850 | */ 1851 | items: CompletionItem[]; 1852 | } 1853 | 1854 | /** 1855 | * The CompletionList namespace provides functions to deal with 1856 | * completion lists. 1857 | */ 1858 | export namespace CompletionList { 1859 | /** 1860 | * Creates a new completion list. 1861 | * 1862 | * @param items The completion items. 1863 | * @param isIncomplete The list is not complete. 1864 | */ 1865 | export function create( 1866 | items?: CompletionItem[], 1867 | isIncomplete?: boolean, 1868 | ): CompletionList { 1869 | return { items: items ? items : [], isIncomplete: !!isIncomplete }; 1870 | } 1871 | } 1872 | 1873 | /** 1874 | * MarkedString can be used to render human readable text. It is either a markdown string 1875 | * or a code-block that provides a language and a code snippet. The language identifier 1876 | * is semantically equal to the optional language identifier in fenced code blocks in GitHub 1877 | * issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting 1878 | * 1879 | * The pair of a language and a value is an equivalent to markdown: 1880 | * ```${language} 1881 | * ${value} 1882 | * ``` 1883 | * 1884 | * Note that markdown strings will be sanitized - that means html will be escaped. 1885 | * @deprecated use MarkupContent instead. 1886 | */ 1887 | export type MarkedString = string | { language: string; value: string }; 1888 | 1889 | export namespace MarkedString { 1890 | /** 1891 | * Creates a marked string from plain text. 1892 | * 1893 | * @param plainText The plain text. 1894 | */ 1895 | export function fromPlainText(plainText: string): string { 1896 | return plainText.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash 1897 | } 1898 | 1899 | /** 1900 | * Checks whether the given value conforms to the [MarkedString](#MarkedString) type. 1901 | */ 1902 | export function is(value: any): value is MarkedString { 1903 | const candidate = value as MarkedString; 1904 | return Is.string(candidate) || 1905 | (Is.objectLiteral(candidate) && Is.string(candidate.language) && 1906 | Is.string(candidate.value)); 1907 | } 1908 | } 1909 | 1910 | /** 1911 | * The result of a hover request. 1912 | */ 1913 | export interface Hover { 1914 | /** 1915 | * The hover's content 1916 | */ 1917 | contents: MarkupContent | MarkedString | MarkedString[]; 1918 | 1919 | /** 1920 | * An optional range 1921 | */ 1922 | range?: Range; 1923 | } 1924 | 1925 | export namespace Hover { 1926 | /** 1927 | * Checks whether the given value conforms to the [Hover](#Hover) interface. 1928 | */ 1929 | export function is(value: any): value is Hover { 1930 | let candidate = value as Hover; 1931 | return !!candidate && Is.objectLiteral(candidate) && ( 1932 | MarkupContent.is(candidate.contents) || 1933 | MarkedString.is(candidate.contents) || 1934 | Is.typedArray(candidate.contents, MarkedString.is) 1935 | ) && ( 1936 | value.range === void 0 || Range.is(value.range) 1937 | ); 1938 | } 1939 | } 1940 | 1941 | /** 1942 | * Represents a parameter of a callable-signature. A parameter can 1943 | * have a label and a doc-comment. 1944 | */ 1945 | export interface ParameterInformation { 1946 | /** 1947 | * The label of this parameter information. 1948 | * 1949 | * Either a string or an inclusive start and exclusive end offsets within its containing 1950 | * signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 1951 | * string representation as `Position` and `Range` does. 1952 | * 1953 | * *Note*: a label of type string should be a substring of its containing signature label. 1954 | * Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. 1955 | */ 1956 | label: string | [number, number]; 1957 | 1958 | /** 1959 | * The human-readable doc-comment of this signature. Will be shown 1960 | * in the UI but can be omitted. 1961 | */ 1962 | documentation?: string | MarkupContent; 1963 | } 1964 | 1965 | /** 1966 | * The ParameterInformation namespace provides helper functions to work with 1967 | * [ParameterInformation](#ParameterInformation) literals. 1968 | */ 1969 | export namespace ParameterInformation { 1970 | /** 1971 | * Creates a new parameter information literal. 1972 | * 1973 | * @param label A label string. 1974 | * @param documentation A doc string. 1975 | */ 1976 | export function create( 1977 | label: string | [number, number], 1978 | documentation?: string, 1979 | ): ParameterInformation { 1980 | return documentation ? { label, documentation } : { label }; 1981 | } 1982 | } 1983 | 1984 | /** 1985 | * Represents the signature of something callable. A signature 1986 | * can have a label, like a function-name, a doc-comment, and 1987 | * a set of parameters. 1988 | */ 1989 | export interface SignatureInformation { 1990 | /** 1991 | * The label of this signature. Will be shown in 1992 | * the UI. 1993 | */ 1994 | label: string; 1995 | 1996 | /** 1997 | * The human-readable doc-comment of this signature. Will be shown 1998 | * in the UI but can be omitted. 1999 | */ 2000 | documentation?: string | MarkupContent; 2001 | 2002 | /** 2003 | * The parameters of this signature. 2004 | */ 2005 | parameters?: ParameterInformation[]; 2006 | } 2007 | 2008 | /** 2009 | * The SignatureInformation namespace provides helper functions to work with 2010 | * [SignatureInformation](#SignatureInformation) literals. 2011 | */ 2012 | export namespace SignatureInformation { 2013 | export function create( 2014 | label: string, 2015 | documentation?: string, 2016 | ...parameters: ParameterInformation[] 2017 | ): SignatureInformation { 2018 | let result: SignatureInformation = { label }; 2019 | if (Is.defined(documentation)) { 2020 | result.documentation = documentation; 2021 | } 2022 | if (Is.defined(parameters)) { 2023 | result.parameters = parameters; 2024 | } else { 2025 | result.parameters = []; 2026 | } 2027 | return result; 2028 | } 2029 | } 2030 | 2031 | /** 2032 | * Signature help represents the signature of something 2033 | * callable. There can be multiple signature but only one 2034 | * active and only one active parameter. 2035 | */ 2036 | export interface SignatureHelp { 2037 | /** 2038 | * One or more signatures. 2039 | */ 2040 | signatures: SignatureInformation[]; 2041 | 2042 | /** 2043 | * The active signature. Set to `null` if no 2044 | * signatures exist. 2045 | */ 2046 | activeSignature: number | null; 2047 | 2048 | /** 2049 | * The active parameter of the active signature. Set to `null` 2050 | * if the active signature has no parameters. 2051 | */ 2052 | activeParameter: number | null; 2053 | } 2054 | 2055 | /** 2056 | * The definition of a symbol represented as one or many [locations](#Location). 2057 | * For most programming languages there is only one location at which a symbol is 2058 | * defined. 2059 | * 2060 | * Servers should prefer returning `DefinitionLink` over `Definition` if supported 2061 | * by the client. 2062 | */ 2063 | export type Definition = Location | Location[]; 2064 | 2065 | /** 2066 | * Information about where a symbol is defined. 2067 | * 2068 | * Provides additional metadata over normal [location](#Location) definitions, including the range of 2069 | * the defining symbol 2070 | */ 2071 | export type DefinitionLink = LocationLink; 2072 | 2073 | /** 2074 | * The declaration of a symbol representation as one or many [locations](#Location). 2075 | */ 2076 | export type Declaration = Location | Location[]; 2077 | 2078 | /** 2079 | * Information about where a symbol is declared. 2080 | * 2081 | * Provides additional metadata over normal [location](#Location) declarations, including the range of 2082 | * the declaring symbol. 2083 | * 2084 | * Servers should prefer returning `DeclarationLink` over `Declaration` if supported 2085 | * by the client. 2086 | */ 2087 | export type DeclarationLink = LocationLink; 2088 | 2089 | /** 2090 | * Value-object that contains additional information when 2091 | * requesting references. 2092 | */ 2093 | export interface ReferenceContext { 2094 | /** 2095 | * Include the declaration of the current symbol. 2096 | */ 2097 | includeDeclaration: boolean; 2098 | } 2099 | 2100 | /** 2101 | * A document highlight kind. 2102 | */ 2103 | export namespace DocumentHighlightKind { 2104 | /** 2105 | * A textual occurrence. 2106 | */ 2107 | export const Text: 1 = 1; 2108 | 2109 | /** 2110 | * Read-access of a symbol, like reading a variable. 2111 | */ 2112 | export const Read: 2 = 2; 2113 | 2114 | /** 2115 | * Write-access of a symbol, like writing to a variable. 2116 | */ 2117 | export const Write: 3 = 3; 2118 | } 2119 | 2120 | export type DocumentHighlightKind = 1 | 2 | 3; 2121 | 2122 | /** 2123 | * A document highlight is a range inside a text document which deserves 2124 | * special attention. Usually a document highlight is visualized by changing 2125 | * the background color of its range. 2126 | */ 2127 | export interface DocumentHighlight { 2128 | /** 2129 | * The range this highlight applies to. 2130 | */ 2131 | range: Range; 2132 | 2133 | /** 2134 | * The highlight kind, default is [text](#DocumentHighlightKind.Text). 2135 | */ 2136 | kind?: DocumentHighlightKind; 2137 | } 2138 | 2139 | /** 2140 | * DocumentHighlight namespace to provide helper functions to work with 2141 | * [DocumentHighlight](#DocumentHighlight) literals. 2142 | */ 2143 | export namespace DocumentHighlight { 2144 | /** 2145 | * Create a DocumentHighlight object. 2146 | * @param range The range the highlight applies to. 2147 | */ 2148 | export function create( 2149 | range: Range, 2150 | kind?: DocumentHighlightKind, 2151 | ): DocumentHighlight { 2152 | let result: DocumentHighlight = { range }; 2153 | if (Is.number(kind)) { 2154 | result.kind = kind; 2155 | } 2156 | return result; 2157 | } 2158 | } 2159 | 2160 | /** 2161 | * A symbol kind. 2162 | */ 2163 | export namespace SymbolKind { 2164 | export const File: 1 = 1; 2165 | export const Module: 2 = 2; 2166 | export const Namespace: 3 = 3; 2167 | export const Package: 4 = 4; 2168 | export const Class: 5 = 5; 2169 | export const Method: 6 = 6; 2170 | export const Property: 7 = 7; 2171 | export const Field: 8 = 8; 2172 | export const Constructor: 9 = 9; 2173 | export const Enum: 10 = 10; 2174 | export const Interface: 11 = 11; 2175 | export const Function: 12 = 12; 2176 | export const Variable: 13 = 13; 2177 | export const Constant: 14 = 14; 2178 | export const String: 15 = 15; 2179 | export const Number: 16 = 16; 2180 | export const Boolean: 17 = 17; 2181 | export const Array: 18 = 18; 2182 | export const Object: 19 = 19; 2183 | export const Key: 20 = 20; 2184 | export const Null: 21 = 21; 2185 | export const EnumMember: 22 = 22; 2186 | export const Struct: 23 = 23; 2187 | export const Event: 24 = 24; 2188 | export const Operator: 25 = 25; 2189 | export const TypeParameter: 26 = 26; 2190 | } 2191 | 2192 | export type SymbolKind = 2193 | | 1 2194 | | 2 2195 | | 3 2196 | | 4 2197 | | 5 2198 | | 6 2199 | | 7 2200 | | 8 2201 | | 9 2202 | | 10 2203 | | 11 2204 | | 12 2205 | | 13 2206 | | 14 2207 | | 15 2208 | | 16 2209 | | 17 2210 | | 18 2211 | | 19 2212 | | 20 2213 | | 21 2214 | | 22 2215 | | 23 2216 | | 24 2217 | | 25 2218 | | 26; 2219 | 2220 | /** 2221 | * Symbol tags are extra annotations that tweak the rendering of a symbol. 2222 | * @since 3.15 2223 | */ 2224 | export namespace SymbolTag { 2225 | /** 2226 | * Render a symbol as obsolete, usually using a strike-out. 2227 | */ 2228 | export const Deprecated: 1 = 1; 2229 | } 2230 | 2231 | export type SymbolTag = 1; 2232 | 2233 | /** 2234 | * Represents information about programming constructs like variables, classes, 2235 | * interfaces etc. 2236 | */ 2237 | export interface SymbolInformation { 2238 | /** 2239 | * The name of this symbol. 2240 | */ 2241 | name: string; 2242 | 2243 | /** 2244 | * The kind of this symbol. 2245 | */ 2246 | kind: SymbolKind; 2247 | 2248 | /** 2249 | * Tags for this completion item. 2250 | * 2251 | * @since 3.16.0 - Proposed state 2252 | */ 2253 | tags?: SymbolTag[]; 2254 | 2255 | /** 2256 | * Indicates if this symbol is deprecated. 2257 | * 2258 | * @deprecated Use tags instead 2259 | */ 2260 | deprecated?: boolean; 2261 | 2262 | /** 2263 | * The location of this symbol. The location's range is used by a tool 2264 | * to reveal the location in the editor. If the symbol is selected in the 2265 | * tool the range's start information is used to position the cursor. So 2266 | * the range usually spans more than the actual symbol's name and does 2267 | * normally include thinks like visibility modifiers. 2268 | * 2269 | * The range doesn't have to denote a node range in the sense of a abstract 2270 | * syntax tree. It can therefore not be used to re-construct a hierarchy of 2271 | * the symbols. 2272 | */ 2273 | location: Location; 2274 | 2275 | /** 2276 | * The name of the symbol containing this symbol. This information is for 2277 | * user interface purposes (e.g. to render a qualifier in the user interface 2278 | * if necessary). It can't be used to re-infer a hierarchy for the document 2279 | * symbols. 2280 | */ 2281 | containerName?: string; 2282 | } 2283 | 2284 | export namespace SymbolInformation { 2285 | /** 2286 | * Creates a new symbol information literal. 2287 | * 2288 | * @param name The name of the symbol. 2289 | * @param kind The kind of the symbol. 2290 | * @param range The range of the location of the symbol. 2291 | * @param uri The resource of the location of symbol, defaults to the current document. 2292 | * @param containerName The name of the symbol containing the symbol. 2293 | */ 2294 | export function create( 2295 | name: string, 2296 | kind: SymbolKind, 2297 | range: Range, 2298 | uri?: string, 2299 | containerName?: string, 2300 | ): SymbolInformation { 2301 | let result: SymbolInformation = { 2302 | name, 2303 | kind, 2304 | location: { uri: uri as any, range }, 2305 | }; 2306 | if (containerName) { 2307 | result.containerName = containerName; 2308 | } 2309 | return result; 2310 | } 2311 | } 2312 | 2313 | /** 2314 | * Represents programming constructs like variables, classes, interfaces etc. 2315 | * that appear in a document. Document symbols can be hierarchical and they 2316 | * have two ranges: one that encloses its definition and one that points to 2317 | * its most interesting range, e.g. the range of an identifier. 2318 | */ 2319 | export interface DocumentSymbol { 2320 | /** 2321 | * The name of this symbol. Will be displayed in the user interface and therefore must not be 2322 | * an empty string or a string only consisting of white spaces. 2323 | */ 2324 | name: string; 2325 | 2326 | /** 2327 | * More detail for this symbol, e.g the signature of a function. 2328 | */ 2329 | detail?: string; 2330 | 2331 | /** 2332 | * The kind of this symbol. 2333 | */ 2334 | kind: SymbolKind; 2335 | 2336 | /** 2337 | * Tags for this completion item. 2338 | * 2339 | * @since 3.16.0 - Proposed state 2340 | */ 2341 | tags?: SymbolTag[]; 2342 | 2343 | /** 2344 | * Indicates if this symbol is deprecated. 2345 | * 2346 | * @deprecated Use tags instead 2347 | */ 2348 | deprecated?: boolean; 2349 | 2350 | /** 2351 | * The range enclosing this symbol not including leading/trailing whitespace but everything else 2352 | * like comments. This information is typically used to determine if the the clients cursor is 2353 | * inside the symbol to reveal in the symbol in the UI. 2354 | */ 2355 | range: Range; 2356 | 2357 | /** 2358 | * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. 2359 | * Must be contained by the the `range`. 2360 | */ 2361 | selectionRange: Range; 2362 | 2363 | /** 2364 | * Children of this symbol, e.g. properties of a class. 2365 | */ 2366 | children?: DocumentSymbol[]; 2367 | } 2368 | 2369 | export namespace DocumentSymbol { 2370 | /** 2371 | * Creates a new symbol information literal. 2372 | * 2373 | * @param name The name of the symbol. 2374 | * @param detail The detail of the symbol. 2375 | * @param kind The kind of the symbol. 2376 | * @param range The range of the symbol. 2377 | * @param selectionRange The selectionRange of the symbol. 2378 | * @param children Children of the symbol. 2379 | */ 2380 | export function create( 2381 | name: string, 2382 | detail: string | undefined, 2383 | kind: SymbolKind, 2384 | range: Range, 2385 | selectionRange: Range, 2386 | children?: DocumentSymbol[], 2387 | ): DocumentSymbol { 2388 | let result: DocumentSymbol = { 2389 | name, 2390 | detail, 2391 | kind, 2392 | range, 2393 | selectionRange, 2394 | }; 2395 | if (children !== void 0) { 2396 | result.children = children; 2397 | } 2398 | return result; 2399 | } 2400 | /** 2401 | * Checks whether the given literal conforms to the [DocumentSymbol](#DocumentSymbol) interface. 2402 | */ 2403 | export function is(value: any): value is DocumentSymbol { 2404 | let candidate: DocumentSymbol = value; 2405 | return candidate && 2406 | Is.string(candidate.name) && Is.number(candidate.kind) && 2407 | Range.is(candidate.range) && Range.is(candidate.selectionRange) && 2408 | (candidate.detail === void 0 || Is.string(candidate.detail)) && 2409 | (candidate.deprecated === void 0 || Is.boolean(candidate.deprecated)) && 2410 | (candidate.children === void 0 || Array.isArray(candidate.children)) && 2411 | (candidate.tags === void 0 || Array.isArray(candidate.tags)); 2412 | } 2413 | } 2414 | 2415 | /** 2416 | * The kind of a code action. 2417 | * 2418 | * Kinds are a hierarchical list of identifiers separated by `.`, e.g. `"refactor.extract.function"`. 2419 | * 2420 | * The set of kinds is open and client needs to announce the kinds it supports to the server during 2421 | * initialization. 2422 | */ 2423 | export type CodeActionKind = string; 2424 | 2425 | /** 2426 | * A set of predefined code action kinds 2427 | */ 2428 | export namespace CodeActionKind { 2429 | /** 2430 | * Empty kind. 2431 | */ 2432 | export const Empty: CodeActionKind = ""; 2433 | 2434 | /** 2435 | * Base kind for quickfix actions: 'quickfix' 2436 | */ 2437 | export const QuickFix: CodeActionKind = "quickfix"; 2438 | 2439 | /** 2440 | * Base kind for refactoring actions: 'refactor' 2441 | */ 2442 | export const Refactor: CodeActionKind = "refactor"; 2443 | 2444 | /** 2445 | * Base kind for refactoring extraction actions: 'refactor.extract' 2446 | * 2447 | * Example extract actions: 2448 | * 2449 | * - Extract method 2450 | * - Extract function 2451 | * - Extract variable 2452 | * - Extract interface from class 2453 | * - ... 2454 | */ 2455 | export const RefactorExtract: CodeActionKind = "refactor.extract"; 2456 | 2457 | /** 2458 | * Base kind for refactoring inline actions: 'refactor.inline' 2459 | * 2460 | * Example inline actions: 2461 | * 2462 | * - Inline function 2463 | * - Inline variable 2464 | * - Inline constant 2465 | * - ... 2466 | */ 2467 | export const RefactorInline: CodeActionKind = "refactor.inline"; 2468 | 2469 | /** 2470 | * Base kind for refactoring rewrite actions: 'refactor.rewrite' 2471 | * 2472 | * Example rewrite actions: 2473 | * 2474 | * - Convert JavaScript function to class 2475 | * - Add or remove parameter 2476 | * - Encapsulate field 2477 | * - Make method static 2478 | * - Move method to base class 2479 | * - ... 2480 | */ 2481 | export const RefactorRewrite: CodeActionKind = "refactor.rewrite"; 2482 | 2483 | /** 2484 | * Base kind for source actions: `source` 2485 | * 2486 | * Source code actions apply to the entire file. 2487 | */ 2488 | export const Source: CodeActionKind = "source"; 2489 | 2490 | /** 2491 | * Base kind for an organize imports source action: `source.organizeImports` 2492 | */ 2493 | export const SourceOrganizeImports: CodeActionKind = "source.organizeImports"; 2494 | 2495 | /** 2496 | * Base kind for auto-fix source actions: `source.fixAll`. 2497 | * 2498 | * Fix all actions automatically fix errors that have a clear fix that do not require user input. 2499 | * They should not suppress errors or perform unsafe fixes such as generating new types or classes. 2500 | * 2501 | * @since 3.15.0 2502 | */ 2503 | export const SourceFixAll: CodeActionKind = "source.fixAll"; 2504 | } 2505 | 2506 | /** 2507 | * Contains additional diagnostic information about the context in which 2508 | * a [code action](#CodeActionProvider.provideCodeActions) is run. 2509 | */ 2510 | export interface CodeActionContext { 2511 | /** 2512 | * An array of diagnostics known on the client side overlapping the range provided to the 2513 | * `textDocument/codeAction` request. They are provied so that the server knows which 2514 | * errors are currently presented to the user for the given range. There is no guarantee 2515 | * that these accurately reflect the error state of the resource. The primary parameter 2516 | * to compute code actions is the provided range. 2517 | */ 2518 | diagnostics: Diagnostic[]; 2519 | 2520 | /** 2521 | * Requested kind of actions to return. 2522 | * 2523 | * Actions not of this kind are filtered out by the client before being shown. So servers 2524 | * can omit computing them. 2525 | */ 2526 | only?: CodeActionKind[]; 2527 | } 2528 | 2529 | /** 2530 | * The CodeActionContext namespace provides helper functions to work with 2531 | * [CodeActionContext](#CodeActionContext) literals. 2532 | */ 2533 | export namespace CodeActionContext { 2534 | /** 2535 | * Creates a new CodeActionContext literal. 2536 | */ 2537 | export function create( 2538 | diagnostics: Diagnostic[], 2539 | only?: CodeActionKind[], 2540 | ): CodeActionContext { 2541 | let result: CodeActionContext = { diagnostics }; 2542 | if (only !== void 0 && only !== null) { 2543 | result.only = only; 2544 | } 2545 | return result; 2546 | } 2547 | /** 2548 | * Checks whether the given literal conforms to the [CodeActionContext](#CodeActionContext) interface. 2549 | */ 2550 | export function is(value: any): value is CodeActionContext { 2551 | let candidate = value as CodeActionContext; 2552 | return Is.defined(candidate) && 2553 | Is.typedArray(candidate.diagnostics, Diagnostic.is) && 2554 | (candidate.only === void 0 || Is.typedArray(candidate.only, Is.string)); 2555 | } 2556 | } 2557 | 2558 | /** 2559 | * A code action represents a change that can be performed in code, e.g. to fix a problem or 2560 | * to refactor code. 2561 | * 2562 | * A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed. 2563 | */ 2564 | export interface CodeAction { 2565 | /** 2566 | * A short, human-readable, title for this code action. 2567 | */ 2568 | title: string; 2569 | 2570 | /** 2571 | * The kind of the code action. 2572 | * 2573 | * Used to filter code actions. 2574 | */ 2575 | kind?: CodeActionKind; 2576 | 2577 | /** 2578 | * The diagnostics that this code action resolves. 2579 | */ 2580 | diagnostics?: Diagnostic[]; 2581 | 2582 | /** 2583 | * Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted 2584 | * by keybindings. 2585 | * 2586 | * A quick fix should be marked preferred if it properly addresses the underlying error. 2587 | * A refactoring should be marked preferred if it is the most reasonable choice of actions to take. 2588 | * 2589 | * @since 3.15.0 2590 | */ 2591 | isPreferred?: boolean; 2592 | 2593 | /** 2594 | * The workspace edit this code action performs. 2595 | */ 2596 | edit?: WorkspaceEdit; 2597 | 2598 | /** 2599 | * A command this code action executes. If a code action 2600 | * provides a edit and a command, first the edit is 2601 | * executed and then the command. 2602 | */ 2603 | command?: Command; 2604 | } 2605 | 2606 | export namespace CodeAction { 2607 | /** 2608 | * Creates a new code action. 2609 | * 2610 | * @param title The title of the code action. 2611 | * @param command The command to execute. 2612 | * @param kind The kind of the code action. 2613 | */ 2614 | export function create( 2615 | title: string, 2616 | command: Command, 2617 | kind?: CodeActionKind, 2618 | ): CodeAction; 2619 | /** 2620 | * Creates a new code action. 2621 | * 2622 | * @param title The title of the code action. 2623 | * @param command The command to execute. 2624 | * @param kind The kind of the code action. 2625 | */ 2626 | export function create( 2627 | title: string, 2628 | edit: WorkspaceEdit, 2629 | kind?: CodeActionKind, 2630 | ): CodeAction; 2631 | export function create( 2632 | title: string, 2633 | commandOrEdit: Command | WorkspaceEdit, 2634 | kind?: CodeActionKind, 2635 | ): CodeAction { 2636 | let result: CodeAction = { title }; 2637 | if (Command.is(commandOrEdit)) { 2638 | result.command = commandOrEdit; 2639 | } else { 2640 | result.edit = commandOrEdit; 2641 | } 2642 | if (kind !== void 0) { 2643 | result.kind = kind; 2644 | } 2645 | return result; 2646 | } 2647 | export function is(value: any): value is CodeAction { 2648 | let candidate: CodeAction = value; 2649 | return candidate && Is.string(candidate.title) && 2650 | (candidate.diagnostics === void 0 || 2651 | Is.typedArray(candidate.diagnostics, Diagnostic.is)) && 2652 | (candidate.kind === void 0 || Is.string(candidate.kind)) && 2653 | (candidate.edit !== void 0 || candidate.command !== void 0) && 2654 | (candidate.command === void 0 || Command.is(candidate.command)) && 2655 | (candidate.isPreferred === void 0 || Is.boolean(candidate.isPreferred)) && 2656 | (candidate.edit === void 0 || WorkspaceEdit.is(candidate.edit)); 2657 | } 2658 | } 2659 | 2660 | /** 2661 | * A code lens represents a [command](#Command) that should be shown along with 2662 | * source text, like the number of references, a way to run tests, etc. 2663 | * 2664 | * A code lens is _unresolved_ when no command is associated to it. For performance 2665 | * reasons the creation of a code lens and resolving should be done to two stages. 2666 | */ 2667 | export interface CodeLens { 2668 | /** 2669 | * The range in which this code lens is valid. Should only span a single line. 2670 | */ 2671 | range: Range; 2672 | 2673 | /** 2674 | * The command this code lens represents. 2675 | */ 2676 | command?: Command; 2677 | 2678 | /** 2679 | * An data entry field that is preserved on a code lens item between 2680 | * a [CodeLensRequest](#CodeLensRequest) and a [CodeLensResolveRequest] 2681 | * (#CodeLensResolveRequest) 2682 | */ 2683 | data?: any; 2684 | } 2685 | 2686 | /** 2687 | * The CodeLens namespace provides helper functions to work with 2688 | * [CodeLens](#CodeLens) literals. 2689 | */ 2690 | export namespace CodeLens { 2691 | /** 2692 | * Creates a new CodeLens literal. 2693 | */ 2694 | export function create(range: Range, data?: any): CodeLens { 2695 | let result: CodeLens = { range }; 2696 | if (Is.defined(data)) result.data = data; 2697 | return result; 2698 | } 2699 | /** 2700 | * Checks whether the given literal conforms to the [CodeLens](#CodeLens) interface. 2701 | */ 2702 | export function is(value: any): value is CodeLens { 2703 | let candidate = value as CodeLens; 2704 | return Is.defined(candidate) && Range.is(candidate.range) && 2705 | (Is.undefined(candidate.command) || Command.is(candidate.command)); 2706 | } 2707 | } 2708 | 2709 | /** 2710 | * Value-object describing what options formatting should use. 2711 | */ 2712 | export interface FormattingOptions { 2713 | /** 2714 | * Size of a tab in spaces. 2715 | */ 2716 | tabSize: number; 2717 | 2718 | /** 2719 | * Prefer spaces over tabs. 2720 | */ 2721 | insertSpaces: boolean; 2722 | 2723 | /** 2724 | * Trim trailing whitespaces on a line. 2725 | * 2726 | * @since 3.15.0 2727 | */ 2728 | trimTrailingWhitespace?: boolean; 2729 | 2730 | /** 2731 | * Insert a newline character at the end of the file if one does not exist. 2732 | * 2733 | * @since 3.15.0 2734 | */ 2735 | insertFinalNewline?: boolean; 2736 | 2737 | /** 2738 | * Trim all newlines after the final newline at the end of the file. 2739 | * 2740 | * @since 3.15.0 2741 | */ 2742 | trimFinalNewlines?: boolean; 2743 | 2744 | /** 2745 | * Signature for further properties. 2746 | */ 2747 | [key: string]: boolean | number | string | undefined; 2748 | } 2749 | 2750 | /** 2751 | * The FormattingOptions namespace provides helper functions to work with 2752 | * [FormattingOptions](#FormattingOptions) literals. 2753 | */ 2754 | export namespace FormattingOptions { 2755 | /** 2756 | * Creates a new FormattingOptions literal. 2757 | */ 2758 | export function create( 2759 | tabSize: number, 2760 | insertSpaces: boolean, 2761 | ): FormattingOptions { 2762 | return { tabSize, insertSpaces }; 2763 | } 2764 | /** 2765 | * Checks whether the given literal conforms to the [FormattingOptions](#FormattingOptions) interface. 2766 | */ 2767 | export function is(value: any): value is FormattingOptions { 2768 | let candidate = value as FormattingOptions; 2769 | return Is.defined(candidate) && Is.number(candidate.tabSize) && 2770 | Is.boolean(candidate.insertSpaces); 2771 | } 2772 | } 2773 | 2774 | /** 2775 | * A document link is a range in a text document that links to an internal or external resource, like another 2776 | * text document or a web site. 2777 | */ 2778 | export interface DocumentLink { 2779 | /** 2780 | * The range this link applies to. 2781 | */ 2782 | range: Range; 2783 | 2784 | /** 2785 | * The uri this link points to. 2786 | */ 2787 | target?: string; 2788 | 2789 | /** 2790 | * The tooltip text when you hover over this link. 2791 | * 2792 | * If a tooltip is provided, is will be displayed in a string that includes instructions on how to 2793 | * trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS, 2794 | * user settings, and localization. 2795 | * 2796 | * @since 3.15.0 2797 | */ 2798 | tooltip?: string; 2799 | 2800 | /** 2801 | * A data entry field that is preserved on a document link between a 2802 | * DocumentLinkRequest and a DocumentLinkResolveRequest. 2803 | */ 2804 | data?: any; 2805 | } 2806 | 2807 | /** 2808 | * The DocumentLink namespace provides helper functions to work with 2809 | * [DocumentLink](#DocumentLink) literals. 2810 | */ 2811 | export namespace DocumentLink { 2812 | /** 2813 | * Creates a new DocumentLink literal. 2814 | */ 2815 | export function create( 2816 | range: Range, 2817 | target?: string, 2818 | data?: any, 2819 | ): DocumentLink { 2820 | return { range, target, data }; 2821 | } 2822 | 2823 | /** 2824 | * Checks whether the given literal conforms to the [DocumentLink](#DocumentLink) interface. 2825 | */ 2826 | export function is(value: any): value is DocumentLink { 2827 | let candidate = value as DocumentLink; 2828 | return Is.defined(candidate) && Range.is(candidate.range) && 2829 | (Is.undefined(candidate.target) || Is.string(candidate.target)); 2830 | } 2831 | } 2832 | 2833 | /** 2834 | * A selection range represents a part of a selection hierarchy. A selection range 2835 | * may have a parent selection range that contains it. 2836 | */ 2837 | export interface SelectionRange { 2838 | /** 2839 | * The [range](#Range) of this selection range. 2840 | */ 2841 | range: Range; 2842 | 2843 | /** 2844 | * The parent selection range containing this range. Therefore `parent.range` must contain `this.range`. 2845 | */ 2846 | parent?: SelectionRange; 2847 | } 2848 | 2849 | /** 2850 | * The SelectionRange namespace provides helper function to work with 2851 | * SelectionRange literals. 2852 | */ 2853 | export namespace SelectionRange { 2854 | /** 2855 | * Creates a new SelectionRange 2856 | * @param range the range. 2857 | * @param parent an optional parent. 2858 | */ 2859 | export function create( 2860 | range: Range, 2861 | parent?: SelectionRange, 2862 | ): SelectionRange { 2863 | return { range, parent }; 2864 | } 2865 | 2866 | export function is(value: any): value is SelectionRange { 2867 | let candidate = value as SelectionRange; 2868 | return candidate !== undefined && Range.is(candidate.range) && 2869 | (candidate.parent === undefined || SelectionRange.is(candidate.parent)); 2870 | } 2871 | } 2872 | 2873 | export const EOL: string[] = ["\n", "\r\n", "\r"]; 2874 | 2875 | /** 2876 | * A simple text document. Not to be implemented. The document keeps the content 2877 | * as string. 2878 | * 2879 | * @deprecated Use the text document from the new vscode-languageserver-textdocument package. 2880 | */ 2881 | export interface TextDocument { 2882 | /** 2883 | * The associated URI for this document. Most documents have the __file__-scheme, indicating that they 2884 | * represent files on disk. However, some documents may have other schemes indicating that they are not 2885 | * available on disk. 2886 | * 2887 | * @readonly 2888 | */ 2889 | readonly uri: DocumentUri; 2890 | 2891 | /** 2892 | * The identifier of the language associated with this document. 2893 | * 2894 | * @readonly 2895 | */ 2896 | readonly languageId: string; 2897 | 2898 | /** 2899 | * The version number of this document (it will increase after each 2900 | * change, including undo/redo). 2901 | * 2902 | * @readonly 2903 | */ 2904 | readonly version: number; 2905 | 2906 | /** 2907 | * Get the text of this document. A substring can be retrieved by 2908 | * providing a range. 2909 | * 2910 | * @param range (optional) An range within the document to return. 2911 | * If no range is passed, the full content is returned. 2912 | * Invalid range positions are adjusted as described in [Position.line](#Position.line) 2913 | * and [Position.character](#Position.character). 2914 | * If the start range position is greater than the end range position, 2915 | * then the effect of getText is as if the two positions were swapped. 2916 | 2917 | * @return The text of this document or a substring of the text if a 2918 | * range is provided. 2919 | */ 2920 | getText(range?: Range): string; 2921 | 2922 | /** 2923 | * Converts a zero-based offset to a position. 2924 | * 2925 | * @param offset A zero-based offset. 2926 | * @return A valid [position](#Position). 2927 | */ 2928 | positionAt(offset: number): Position; 2929 | 2930 | /** 2931 | * Converts the position to a zero-based offset. 2932 | * Invalid positions are adjusted as described in [Position.line](#Position.line) 2933 | * and [Position.character](#Position.character). 2934 | * 2935 | * @param position A position. 2936 | * @return A valid zero-based offset. 2937 | */ 2938 | offsetAt(position: Position): number; 2939 | 2940 | /** 2941 | * The number of lines in this document. 2942 | * 2943 | * @readonly 2944 | */ 2945 | readonly lineCount: number; 2946 | } 2947 | 2948 | /** 2949 | * @deprecated Use the text document from the new vscode-languageserver-textdocument package. 2950 | */ 2951 | export namespace TextDocument { 2952 | /** 2953 | * Creates a new ITextDocument literal from the given uri and content. 2954 | * @param uri The document's uri. 2955 | * @param languageId The document's language Id. 2956 | * @param content The document's content. 2957 | */ 2958 | export function create( 2959 | uri: DocumentUri, 2960 | languageId: string, 2961 | version: number, 2962 | content: string, 2963 | ): TextDocument { 2964 | return new FullTextDocument(uri, languageId, version, content); 2965 | } 2966 | /** 2967 | * Checks whether the given literal conforms to the [ITextDocument](#ITextDocument) interface. 2968 | */ 2969 | export function is(value: any): value is TextDocument { 2970 | let candidate = value as TextDocument; 2971 | return Is.defined(candidate) && Is.string(candidate.uri) && 2972 | (Is.undefined(candidate.languageId) || Is.string(candidate.languageId)) && 2973 | Is.number(candidate.lineCount) && 2974 | Is.func(candidate.getText) && Is.func(candidate.positionAt) && 2975 | Is.func(candidate.offsetAt) 2976 | ? true 2977 | : false; 2978 | } 2979 | 2980 | export function applyEdits( 2981 | document: TextDocument, 2982 | edits: TextEdit[], 2983 | ): string { 2984 | let text = document.getText(); 2985 | let sortedEdits = mergeSort(edits, (a, b) => { 2986 | let diff = a.range.start.line - b.range.start.line; 2987 | if (diff === 0) { 2988 | return a.range.start.character - b.range.start.character; 2989 | } 2990 | return diff; 2991 | }); 2992 | let lastModifiedOffset = text.length; 2993 | for (let i = sortedEdits.length - 1; i >= 0; i--) { 2994 | let e = sortedEdits[i]; 2995 | let startOffset = document.offsetAt(e.range.start); 2996 | let endOffset = document.offsetAt(e.range.end); 2997 | if (endOffset <= lastModifiedOffset) { 2998 | text = text.substring(0, startOffset) + e.newText + 2999 | text.substring(endOffset, text.length); 3000 | } else { 3001 | throw new Error("Overlapping edit"); 3002 | } 3003 | lastModifiedOffset = startOffset; 3004 | } 3005 | return text; 3006 | } 3007 | 3008 | function mergeSort(data: T[], compare: (a: T, b: T) => number): T[] { 3009 | if (data.length <= 1) { 3010 | // sorted 3011 | return data; 3012 | } 3013 | const p = (data.length / 2) | 0; 3014 | const left = data.slice(0, p); 3015 | const right = data.slice(p); 3016 | 3017 | mergeSort(left, compare); 3018 | mergeSort(right, compare); 3019 | 3020 | let leftIdx = 0; 3021 | let rightIdx = 0; 3022 | let i = 0; 3023 | while (leftIdx < left.length && rightIdx < right.length) { 3024 | let ret = compare(left[leftIdx], right[rightIdx]); 3025 | if (ret <= 0) { 3026 | // smaller_equal -> take left to preserve order 3027 | data[i++] = left[leftIdx++]; 3028 | } else { 3029 | // greater -> take right 3030 | data[i++] = right[rightIdx++]; 3031 | } 3032 | } 3033 | while (leftIdx < left.length) { 3034 | data[i++] = left[leftIdx++]; 3035 | } 3036 | while (rightIdx < right.length) { 3037 | data[i++] = right[rightIdx++]; 3038 | } 3039 | return data; 3040 | } 3041 | } 3042 | 3043 | /** 3044 | * @deprecated No longer used, use TextDocumentChangeEvent from vscode-languageserver instead. 3045 | */ 3046 | export interface TextDocumentChangeEvent { 3047 | /** 3048 | * The document that has changed. 3049 | */ 3050 | document: TextDocument; 3051 | } 3052 | 3053 | /** 3054 | * Represents reasons why a text document is saved. 3055 | * @deprecated No longer used, use TextDocumentWillSaveEvent from vscode-languageserver instead. 3056 | */ 3057 | export interface TextDocumentWillSaveEvent { 3058 | /** 3059 | * The document that will be saved 3060 | */ 3061 | document: TextDocument; 3062 | 3063 | /** 3064 | * The reason why save was triggered. 3065 | */ 3066 | reason: 1 | 2 | 3; 3067 | } 3068 | 3069 | /** 3070 | * An event describing a change to a text document. If range and rangeLength are omitted 3071 | * the new text is considered to be the full content of the document. 3072 | */ 3073 | type TextDocumentContentChangeEvent = { 3074 | /** 3075 | * The range of the document that changed. 3076 | */ 3077 | range: Range; 3078 | 3079 | /** 3080 | * The optional length of the range that got replaced. 3081 | * 3082 | * @deprecated use range instead. 3083 | */ 3084 | rangeLength?: number; 3085 | 3086 | /** 3087 | * The new text for the provided range. 3088 | */ 3089 | text: string; 3090 | } | { 3091 | /** 3092 | * The new text of the whole document. 3093 | */ 3094 | text: string; 3095 | }; 3096 | 3097 | class FullTextDocument implements TextDocument { 3098 | private _uri: DocumentUri; 3099 | private _languageId: string; 3100 | private _version: number; 3101 | private _content: string; 3102 | private _lineOffsets: number[] | undefined; 3103 | 3104 | public constructor( 3105 | uri: DocumentUri, 3106 | languageId: string, 3107 | version: number, 3108 | content: string, 3109 | ) { 3110 | this._uri = uri; 3111 | this._languageId = languageId; 3112 | this._version = version; 3113 | this._content = content; 3114 | this._lineOffsets = undefined; 3115 | } 3116 | 3117 | public get uri(): string { 3118 | return this._uri; 3119 | } 3120 | 3121 | public get languageId(): string { 3122 | return this._languageId; 3123 | } 3124 | 3125 | public get version(): number { 3126 | return this._version; 3127 | } 3128 | 3129 | public getText(range?: Range): string { 3130 | if (range) { 3131 | let start = this.offsetAt(range.start); 3132 | let end = this.offsetAt(range.end); 3133 | return this._content.substring(start, end); 3134 | } 3135 | return this._content; 3136 | } 3137 | 3138 | public update(event: TextDocumentContentChangeEvent, version: number): void { 3139 | this._content = event.text; 3140 | this._version = version; 3141 | this._lineOffsets = undefined; 3142 | } 3143 | 3144 | private getLineOffsets(): number[] { 3145 | if (this._lineOffsets === undefined) { 3146 | let lineOffsets: number[] = []; 3147 | let text = this._content; 3148 | let isLineStart = true; 3149 | for (let i = 0; i < text.length; i++) { 3150 | if (isLineStart) { 3151 | lineOffsets.push(i); 3152 | isLineStart = false; 3153 | } 3154 | let ch = text.charAt(i); 3155 | isLineStart = (ch === "\r" || ch === "\n"); 3156 | if (ch === "\r" && i + 1 < text.length && text.charAt(i + 1) === "\n") { 3157 | i++; 3158 | } 3159 | } 3160 | if (isLineStart && text.length > 0) { 3161 | lineOffsets.push(text.length); 3162 | } 3163 | this._lineOffsets = lineOffsets; 3164 | } 3165 | return this._lineOffsets; 3166 | } 3167 | 3168 | public positionAt(offset: number) { 3169 | offset = Math.max(Math.min(offset, this._content.length), 0); 3170 | 3171 | let lineOffsets = this.getLineOffsets(); 3172 | let low = 0, high = lineOffsets.length; 3173 | if (high === 0) { 3174 | return Position.create(0, offset); 3175 | } 3176 | while (low < high) { 3177 | let mid = Math.floor((low + high) / 2); 3178 | if (lineOffsets[mid] > offset) { 3179 | high = mid; 3180 | } else { 3181 | low = mid + 1; 3182 | } 3183 | } 3184 | // low is the least x for which the line offset is larger than the current offset 3185 | // or array.length if no line offset is larger than the current offset 3186 | let line = low - 1; 3187 | return Position.create(line, offset - lineOffsets[line]); 3188 | } 3189 | 3190 | public offsetAt(position: Position) { 3191 | let lineOffsets = this.getLineOffsets(); 3192 | if (position.line >= lineOffsets.length) { 3193 | return this._content.length; 3194 | } else if (position.line < 0) { 3195 | return 0; 3196 | } 3197 | let lineOffset = lineOffsets[position.line]; 3198 | let nextLineOffset = (position.line + 1 < lineOffsets.length) 3199 | ? lineOffsets[position.line + 1] 3200 | : this._content.length; 3201 | return Math.max( 3202 | Math.min(lineOffset + position.character, nextLineOffset), 3203 | lineOffset, 3204 | ); 3205 | } 3206 | 3207 | public get lineCount() { 3208 | return this.getLineOffsets().length; 3209 | } 3210 | } 3211 | 3212 | namespace Is { 3213 | const toString = Object.prototype.toString; 3214 | 3215 | export function defined(value: any): boolean { 3216 | return typeof value !== "undefined"; 3217 | } 3218 | 3219 | export function undefined(value: any): boolean { 3220 | return typeof value === "undefined"; 3221 | } 3222 | 3223 | export function boolean(value: any): value is boolean { 3224 | return value === true || value === false; 3225 | } 3226 | 3227 | export function string(value: any): value is string { 3228 | return toString.call(value) === "[object String]"; 3229 | } 3230 | 3231 | export function number(value: any): value is number { 3232 | return toString.call(value) === "[object Number]"; 3233 | } 3234 | 3235 | export function func(value: any): value is Function { 3236 | return toString.call(value) === "[object Function]"; 3237 | } 3238 | 3239 | export function objectLiteral(value: any): value is object { 3240 | // Strictly speaking class instances pass this check as well. Since the LSP 3241 | // doesn't use classes we ignore this for now. If we do we need to add something 3242 | // like this: `Object.getPrototypeOf(Object.getPrototypeOf(x)) === null` 3243 | return value !== null && typeof value === "object"; 3244 | } 3245 | 3246 | export function typedArray( 3247 | value: any, 3248 | check: (value: any) => boolean, 3249 | ): value is T[] { 3250 | return Array.isArray(value) && ( value).every(check); 3251 | } 3252 | } 3253 | -------------------------------------------------------------------------------- /types/test/edits_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertThrows, 4 | } from "https://deno.land/std/testing/asserts.ts"; 5 | import { TextDocument, TextEdit, Position, Range } from "../mod.ts"; 6 | 7 | const applyEdits = TextDocument.applyEdits; 8 | 9 | Deno.test(function editsInserts(): void { 10 | let input = TextDocument.create( 11 | "foo://bar/f", 12 | "html", 13 | 0, 14 | "012345678901234567890123456789", 15 | ); 16 | assertEquals( 17 | applyEdits(input, [TextEdit.insert(Position.create(0, 0), "Hello")]), 18 | "Hello012345678901234567890123456789", 19 | ); 20 | assertEquals( 21 | applyEdits(input, [TextEdit.insert(Position.create(0, 1), "Hello")]), 22 | "0Hello12345678901234567890123456789", 23 | ); 24 | assertEquals( 25 | applyEdits( 26 | input, 27 | [ 28 | TextEdit.insert(Position.create(0, 1), "Hello"), 29 | TextEdit.insert(Position.create(0, 1), "World"), 30 | ], 31 | ), 32 | "0HelloWorld12345678901234567890123456789", 33 | ); 34 | assertEquals( 35 | applyEdits( 36 | input, 37 | [ 38 | TextEdit.insert(Position.create(0, 2), "One"), 39 | TextEdit.insert(Position.create(0, 1), "Hello"), 40 | TextEdit.insert(Position.create(0, 1), "World"), 41 | TextEdit.insert(Position.create(0, 2), "Two"), 42 | TextEdit.insert(Position.create(0, 2), "Three"), 43 | ], 44 | ), 45 | "0HelloWorld1OneTwoThree2345678901234567890123456789", 46 | ); 47 | }); 48 | 49 | Deno.test(function editsReplace(): void { 50 | let input = TextDocument.create( 51 | "foo://bar/f", 52 | "html", 53 | 0, 54 | "012345678901234567890123456789", 55 | ); 56 | assertEquals( 57 | applyEdits( 58 | input, 59 | [TextEdit.replace( 60 | Range.create(Position.create(0, 3), Position.create(0, 6)), 61 | "Hello", 62 | )], 63 | ), 64 | "012Hello678901234567890123456789", 65 | ); 66 | assertEquals( 67 | applyEdits( 68 | input, 69 | [ 70 | TextEdit.replace( 71 | Range.create(Position.create(0, 3), Position.create(0, 6)), 72 | "Hello", 73 | ), 74 | TextEdit.replace( 75 | Range.create(Position.create(0, 6), Position.create(0, 9)), 76 | "World", 77 | ), 78 | ], 79 | ), 80 | "012HelloWorld901234567890123456789", 81 | ); 82 | assertEquals( 83 | applyEdits( 84 | input, 85 | [ 86 | TextEdit.replace( 87 | Range.create(Position.create(0, 3), Position.create(0, 6)), 88 | "Hello", 89 | ), 90 | TextEdit.insert(Position.create(0, 6), "World"), 91 | ], 92 | ), 93 | "012HelloWorld678901234567890123456789", 94 | ); 95 | assertEquals( 96 | applyEdits( 97 | input, 98 | [ 99 | TextEdit.insert(Position.create(0, 6), "World"), 100 | TextEdit.replace( 101 | Range.create(Position.create(0, 3), Position.create(0, 6)), 102 | "Hello", 103 | ), 104 | ], 105 | ), 106 | "012HelloWorld678901234567890123456789", 107 | ); 108 | assertEquals( 109 | applyEdits( 110 | input, 111 | [ 112 | TextEdit.insert(Position.create(0, 3), "World"), 113 | TextEdit.replace( 114 | Range.create(Position.create(0, 3), Position.create(0, 6)), 115 | "Hello", 116 | ), 117 | ], 118 | ), 119 | "012WorldHello678901234567890123456789", 120 | ); 121 | }); 122 | 123 | Deno.test(function editsOverlap(): void { 124 | let input = TextDocument.create( 125 | "foo://bar/f", 126 | "html", 127 | 0, 128 | "012345678901234567890123456789", 129 | ); 130 | assertThrows(() => 131 | applyEdits( 132 | input, 133 | [ 134 | TextEdit.replace( 135 | Range.create(Position.create(0, 3), Position.create(0, 6)), 136 | "Hello", 137 | ), 138 | TextEdit.insert(Position.create(0, 3), "World"), 139 | ], 140 | ) 141 | ); 142 | assertThrows(() => 143 | applyEdits( 144 | input, 145 | [ 146 | TextEdit.replace( 147 | Range.create(Position.create(0, 3), Position.create(0, 6)), 148 | "Hello", 149 | ), 150 | TextEdit.insert(Position.create(0, 4), "World"), 151 | ], 152 | ) 153 | ); 154 | }); 155 | 156 | Deno.test(function editsMultiline(): void { 157 | let input = TextDocument.create("foo://bar/f", "html", 0, "0\n1\n2\n3\n4"); 158 | assertEquals( 159 | applyEdits( 160 | input, 161 | [ 162 | TextEdit.replace( 163 | Range.create(Position.create(2, 0), Position.create(3, 0)), 164 | "Hello", 165 | ), 166 | TextEdit.insert(Position.create(1, 1), "World"), 167 | ], 168 | ), 169 | "0\n1World\nHello3\n4", 170 | ); 171 | }); 172 | -------------------------------------------------------------------------------- /types/test/textdocument_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertStrictEq, 4 | assertThrows, 5 | } from "https://deno.land/std/testing/asserts.ts"; 6 | import { TextDocument, Range, Position } from "../mod.ts"; 7 | 8 | function newDocument(str: string): TextDocument { 9 | return TextDocument.create("file://foo/bar", "text", 0, str); 10 | } 11 | 12 | Deno.test("TextDocument - Single line", () => { 13 | var str = "Hello World"; 14 | var lm = newDocument(str); 15 | assertEquals(lm.lineCount, 1); 16 | 17 | for (var i = 0; i < str.length; i++) { 18 | assertEquals(lm.offsetAt(Position.create(0, i)), i); 19 | assertEquals(lm.positionAt(i), Position.create(0, i)); 20 | } 21 | }); 22 | 23 | Deno.test("TextDocument - Multiple lines", () => { 24 | var str = "ABCDE\nFGHIJ\nKLMNO\n"; 25 | var lm = newDocument(str); 26 | assertEquals(lm.lineCount, 4); 27 | 28 | for (var i = 0; i < str.length; i++) { 29 | var line = Math.floor(i / 6); 30 | var column = i % 6; 31 | 32 | assertEquals(lm.offsetAt(Position.create(line, column)), i); 33 | assertEquals(lm.positionAt(i), Position.create(line, column)); 34 | } 35 | 36 | assertEquals(lm.offsetAt(Position.create(3, 0)), 18); 37 | assertEquals(lm.offsetAt(Position.create(3, 1)), 18); 38 | assertEquals(lm.positionAt(18), Position.create(3, 0)); 39 | assertEquals(lm.positionAt(19), Position.create(3, 0)); 40 | }); 41 | 42 | Deno.test("TextDocument - New line characters", () => { 43 | var str = "ABCDE\rFGHIJ"; 44 | assertEquals(newDocument(str).lineCount, 2); 45 | 46 | var str = "ABCDE\nFGHIJ"; 47 | assertEquals(newDocument(str).lineCount, 2); 48 | 49 | var str = "ABCDE\r\nFGHIJ"; 50 | assertEquals(newDocument(str).lineCount, 2); 51 | 52 | str = "ABCDE\n\nFGHIJ"; 53 | assertEquals(newDocument(str).lineCount, 3); 54 | 55 | str = "ABCDE\r\rFGHIJ"; 56 | assertEquals(newDocument(str).lineCount, 3); 57 | 58 | str = "ABCDE\n\rFGHIJ"; 59 | assertEquals(newDocument(str).lineCount, 3); 60 | }); 61 | 62 | Deno.test("TextDocument - getText(Range)", () => { 63 | var str = "12345\n12345\n12345"; 64 | var lm = newDocument(str); 65 | assertEquals(lm.getText(), str); 66 | assertEquals(lm.getText(Range.create(-1, 0, 0, 5)), "12345"); 67 | assertEquals(lm.getText(Range.create(0, 0, 0, 5)), "12345"); 68 | assertEquals(lm.getText(Range.create(0, 4, 1, 1)), "5\n1"); 69 | assertEquals(lm.getText(Range.create(0, 4, 2, 1)), "5\n12345\n1"); 70 | assertEquals(lm.getText(Range.create(0, 4, 3, 1)), "5\n12345\n12345"); 71 | assertEquals(lm.getText(Range.create(0, 0, 3, 5)), str); 72 | }); 73 | 74 | Deno.test("TextDocument - Invalid inputs", () => { 75 | var str = "Hello World"; 76 | var lm = newDocument(str); 77 | 78 | // invalid position 79 | assertEquals(lm.offsetAt(Position.create(0, str.length)), str.length); 80 | assertEquals(lm.offsetAt(Position.create(0, str.length + 3)), str.length); 81 | assertEquals(lm.offsetAt(Position.create(2, 3)), str.length); 82 | assertEquals(lm.offsetAt(Position.create(-1, 3)), 0); 83 | assertEquals(lm.offsetAt(Position.create(0, -3)), 0); 84 | assertEquals(lm.offsetAt(Position.create(1, -3)), str.length); 85 | 86 | // invalid offsets 87 | assertEquals(lm.positionAt(-1), Position.create(0, 0)); 88 | assertEquals(lm.positionAt(str.length), Position.create(0, str.length)); 89 | assertEquals(lm.positionAt(str.length + 3), Position.create(0, str.length)); 90 | }); 91 | -------------------------------------------------------------------------------- /types/test/typeguards_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | import { Range, Position, Hover, MarkedString, TextEdit } from "../mod.ts"; 3 | 4 | const { test } = Deno; 5 | 6 | test("Position", () => { 7 | const position: Position = { 8 | line: 0, 9 | character: 0, 10 | }; 11 | assertEquals(Position.is(position), true); 12 | }); 13 | 14 | test("Position - empty object", () => { 15 | const position = {}; 16 | assertEquals(Position.is(position), false); 17 | }); 18 | 19 | test("Position - missing character", () => { 20 | const position = { 21 | line: 0, 22 | }; 23 | assertEquals(Position.is(position), false); 24 | }); 25 | 26 | test("Position - null", () => { 27 | const position = null; 28 | assertEquals(Position.is(position), false); 29 | }); 30 | 31 | test("Position - undefined", () => { 32 | const position = undefined; 33 | assertEquals(Position.is(position), false); 34 | }); 35 | 36 | test("Range", () => { 37 | const range: Range = { 38 | start: { 39 | line: 0, 40 | character: 0, 41 | }, 42 | end: { 43 | line: 1, 44 | character: 1, 45 | }, 46 | }; 47 | assertEquals(Range.is(range), true); 48 | }); 49 | 50 | test("Range - empty object", () => { 51 | const range = {}; 52 | assertEquals(Range.is(range), false); 53 | }); 54 | 55 | test("Range - null", () => { 56 | const range = null; 57 | assertEquals(Range.is(range), false); 58 | }); 59 | 60 | test("Range - undefined", () => { 61 | const range = undefined; 62 | assertEquals(Range.is(range), false); 63 | }); 64 | 65 | test("MarkedString - string", () => { 66 | const markedString = "test"; 67 | assertEquals(MarkedString.is(markedString), true); 68 | }); 69 | 70 | test("MarkedString - language and value", () => { 71 | const markedString = { language: "foo", value: "test" }; 72 | assertEquals(MarkedString.is(markedString), true); 73 | }); 74 | 75 | test("MarkedString - null", () => { 76 | const markedString = null; 77 | assertEquals(MarkedString.is(markedString), false); 78 | }); 79 | 80 | test("MarkedString - undefined", () => { 81 | const markedString = undefined; 82 | assertEquals(MarkedString.is(markedString), false); 83 | }); 84 | 85 | test("Hover - string contents", () => { 86 | const hover = { 87 | contents: "test", 88 | }; 89 | assertEquals(Hover.is(hover), true); 90 | }); 91 | 92 | test("Hover - MarkupContent contents", () => { 93 | const hover = { 94 | contents: { 95 | kind: "plaintext", 96 | value: "test", 97 | }, 98 | }; 99 | assertEquals(Hover.is(hover), true); 100 | }); 101 | 102 | test("Hover - MarkupContent contents array", () => { 103 | const hover = { 104 | contents: [ 105 | { 106 | kind: "plaintext", 107 | value: "test", 108 | }, 109 | ], 110 | }; 111 | assertEquals(Hover.is(hover), false); 112 | }); 113 | 114 | test("Hover - contents array", () => { 115 | const hover = { 116 | contents: [ 117 | "test", 118 | { 119 | language: "foo", 120 | value: "test", 121 | }, 122 | ], 123 | }; 124 | assertEquals(Hover.is(hover), true); 125 | }); 126 | 127 | test("Hover - null range", () => { 128 | const hover = { 129 | contents: "test", 130 | range: null, 131 | }; 132 | assertEquals(Hover.is(hover), false); 133 | }); 134 | 135 | test("Hover - null contents", () => { 136 | const hover = { 137 | contents: null, 138 | }; 139 | assertEquals(Hover.is(hover), false); 140 | }); 141 | 142 | test("Hover - contents array with null", () => { 143 | const hover = { 144 | contents: [null], 145 | }; 146 | assertEquals(Hover.is(hover), false); 147 | }); 148 | 149 | test("Hover - null", () => { 150 | const hover = null; 151 | assertEquals(Hover.is(hover), false); 152 | }); 153 | 154 | test("Hover - undefined", () => { 155 | const hover = undefined; 156 | assertEquals(Hover.is(hover), false); 157 | }); 158 | 159 | test("TextEdit - string contents, range defined", () => { 160 | const edit = { 161 | newText: "test", 162 | range: Range.create(Position.create(0, 0), Position.create(0, 1)), 163 | }; 164 | assertEquals(TextEdit.is(edit), true); 165 | }); 166 | 167 | test("TextEdit - string contents, range undefined", () => { 168 | const edit = { 169 | newText: "test", 170 | range: undefined, 171 | }; 172 | assertEquals(TextEdit.is(edit), false); 173 | }); 174 | 175 | test("TextEdit - string contents, range null", () => { 176 | const edit = { 177 | newText: "test", 178 | range: null, 179 | }; 180 | assertEquals(TextEdit.is(edit), false); 181 | }); 182 | 183 | test("TextEdit - null contents, range defined", () => { 184 | const edit = { 185 | contents: null, 186 | range: Range.create(Position.create(0, 0), Position.create(0, 1)), 187 | }; 188 | assertEquals(TextEdit.is(edit), false); 189 | }); 190 | 191 | test("TextEdit - undefined contents, range defined", () => { 192 | const edit = { 193 | contents: undefined, 194 | range: Range.create(Position.create(0, 0), Position.create(0, 1)), 195 | }; 196 | assertEquals(TextEdit.is(edit), false); 197 | }); 198 | 199 | test("TextEdit - null", () => { 200 | const edit = null; 201 | assertEquals(TextEdit.is(edit), false); 202 | }); 203 | 204 | test("TextEdit - undefined", () => { 205 | const edit = undefined; 206 | assertEquals(TextEdit.is(edit), false); 207 | }); 208 | --------------------------------------------------------------------------------