diff --git a/package-lock.json b/package-lock.json index 9a6050f8..dba43e53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "dev": true }, "monaco-editor-core": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/monaco-editor-core/-/monaco-editor-core-0.18.1.tgz", - "integrity": "sha512-euzXzmwjZFG0oAPGjICMwINcZBzQDyfGDYlAR5YNMBJZO9Bmkqq1xpTTze/qQ0KKbVmawFXiwgUbg7WVgebP9Q==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/monaco-editor-core/-/monaco-editor-core-0.19.0.tgz", + "integrity": "sha512-wD60zpYDhsuJbzzLQigAteK2cA5fkuaDn+4c7NLwm/526OX5eL6MMvLhfvRgrDvLO00SYhFf6vz1y1C8M1hTpQ==", "dev": true }, "monaco-languages": { diff --git a/package.json b/package.json index dacf8ed4..88993d31 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "url": "https://github.com/Microsoft/monaco-typescript/issues" }, "devDependencies": { - "monaco-editor-core": "^0.18.1", + "monaco-editor-core": "^0.19.0", "monaco-languages": "^1.8.0", "monaco-plugin-helpers": "^1.0.2", "requirejs": "^2.3.6", diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index d8834827..554ab670 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -11,7 +11,6 @@ import { TypeScriptWorker } from './tsWorker'; import Uri = monaco.Uri; import Position = monaco.Position; import Range = monaco.Range; -import Thenable = monaco.Thenable; import CancellationToken = monaco.CancellationToken; import IDisposable = monaco.IDisposable; @@ -48,7 +47,7 @@ export function flattenDiagnosticMessageText(diag: string | ts.DiagnosticMessage return result; } -function displayPartsToString(displayParts: ts.SymbolDisplayPart[]): string { +function displayPartsToString(displayParts: ts.SymbolDisplayPart[] | undefined): string { if (displayParts) { return displayParts.map((displayPart) => displayPart.text).join(""); } @@ -62,19 +61,17 @@ export abstract class Adapter { constructor(protected _worker: (first: Uri, ...more: Uri[]) => Promise) { } - protected _positionToOffset(uri: Uri, position: monaco.IPosition): number { - let model = monaco.editor.getModel(uri); - return model.getOffsetAt(position); - } + // protected _positionToOffset(model: monaco.editor.ITextModel, position: monaco.IPosition): number { + // return model.getOffsetAt(position); + // } - protected _offsetToPosition(uri: Uri, offset: number): monaco.IPosition { - let model = monaco.editor.getModel(uri); - return model.getPositionAt(offset); - } + // protected _offsetToPosition(model: monaco.editor.ITextModel, offset: number): monaco.IPosition { + // return model.getPositionAt(offset); + // } - protected _textSpanToRange(uri: Uri, span: ts.TextSpan): monaco.IRange { - let p1 = this._offsetToPosition(uri, span.start); - let p2 = this._offsetToPosition(uri, span.start + span.length); + protected _textSpanToRange(model: monaco.editor.ITextModel, span: ts.TextSpan): monaco.IRange { + let p1 = model.getPositionAt(span.start); + let p2 = model.getPositionAt(span.start + span.length); let { lineNumber: startLineNumber, column: startColumn } = p1; let { lineNumber: endLineNumber, column: endColumn } = p2; return { startLineNumber, startColumn, endLineNumber, endColumn }; @@ -108,7 +105,7 @@ export class DiagnosticsAdapter extends Adapter { let handle: number; const changeSubscription = model.onDidChangeContent(() => { clearTimeout(handle); - handle = setTimeout(() => this._doValidate(model.uri), 500); + handle = setTimeout(() => this._doValidate(model), 500); }); this._listener[model.uri.toString()] = { @@ -118,7 +115,7 @@ export class DiagnosticsAdapter extends Adapter { } }; - this._doValidate(model.uri); + this._doValidate(model); }; const onModelRemoved = (model: monaco.editor.IModel): void => { @@ -163,43 +160,46 @@ export class DiagnosticsAdapter extends Adapter { this._disposables = []; } - private _doValidate(resource: Uri): void { - this._worker(resource).then(worker => { - if (!monaco.editor.getModel(resource)) { - // model was disposed in the meantime - return null; - } - const promises: Promise[] = []; - const { noSyntaxValidation, noSemanticValidation, noSuggestionDiagnostics } = this._defaults.getDiagnosticsOptions(); - if (!noSyntaxValidation) { - promises.push(worker.getSyntacticDiagnostics(resource.toString())); - } - if (!noSemanticValidation) { - promises.push(worker.getSemanticDiagnostics(resource.toString())); - } - if (!noSuggestionDiagnostics) { - promises.push(worker.getSuggestionDiagnostics(resource.toString())); - } - return Promise.all(promises); - }).then(diagnostics => { - if (!diagnostics || !monaco.editor.getModel(resource)) { - // model was disposed in the meantime - return null; - } - const markers = diagnostics - .reduce((p, c) => c.concat(p), []) - .filter(d => (this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore || []).indexOf(d.code) === -1) - .map(d => this._convertDiagnostics(resource, d)); - - monaco.editor.setModelMarkers(monaco.editor.getModel(resource), this._selector, markers); - }).then(undefined, err => { - console.error(err); - }); + private async _doValidate(model: monaco.editor.ITextModel): Promise { + const worker = await this._worker(model.uri); + + if (model.isDisposed()) { + // model was disposed in the meantime + return; + } + + const promises: Promise[] = []; + const { noSyntaxValidation, noSemanticValidation, noSuggestionDiagnostics } = this._defaults.getDiagnosticsOptions(); + if (!noSyntaxValidation) { + promises.push(worker.getSyntacticDiagnostics(model.uri.toString())); + } + if (!noSemanticValidation) { + promises.push(worker.getSemanticDiagnostics(model.uri.toString())); + } + if (!noSuggestionDiagnostics) { + promises.push(worker.getSuggestionDiagnostics(model.uri.toString())); + } + + const diagnostics = await Promise.all(promises); + + if (!diagnostics || model.isDisposed()) { + // model was disposed in the meantime + return; + } + + const markers = diagnostics + .reduce((p, c) => c.concat(p), []) + .filter(d => (this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore || []).indexOf(d.code) === -1) + .map(d => this._convertDiagnostics(model, d)); + + monaco.editor.setModelMarkers(model, this._selector, markers); } - private _convertDiagnostics(resource: Uri, diag: ts.Diagnostic): monaco.editor.IMarkerData { - const { lineNumber: startLineNumber, column: startColumn } = this._offsetToPosition(resource, diag.start); - const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(resource, diag.start + diag.length); + private _convertDiagnostics(model: monaco.editor.ITextModel, diag: ts.Diagnostic): monaco.editor.IMarkerData { + const diagStart = diag.start || 0; + const diagLength = diag.length || 1; + const { lineNumber: startLineNumber, column: startColumn } = model.getPositionAt(diagStart); + const { lineNumber: endLineNumber, column: endColumn } = model.getPositionAt(diagStart + diagLength); return { severity: this._tsDiagnosticCategoryToMarkerSeverity(diag.category), @@ -210,28 +210,41 @@ export class DiagnosticsAdapter extends Adapter { message: flattenDiagnosticMessageText(diag.messageText, '\n'), code: diag.code.toString(), tags: diag.reportsUnnecessary ? [monaco.MarkerTag.Unnecessary] : [], - relatedInformation: this._convertRelatedInformation(resource, diag.relatedInformation), + relatedInformation: this._convertRelatedInformation(model, diag.relatedInformation), }; } - private _convertRelatedInformation(resource: Uri, relatedInformation?: ts.DiagnosticRelatedInformation[]): monaco.editor.IRelatedInformation[] { - if (relatedInformation === undefined) - return undefined; + private _convertRelatedInformation(model: monaco.editor.ITextModel, relatedInformation?: ts.DiagnosticRelatedInformation[]): monaco.editor.IRelatedInformation[] | undefined { + if (!relatedInformation) { + return; + } - return relatedInformation.map(info => { - const relatedResource = info.file === undefined ? resource : monaco.Uri.parse(info.file.fileName); - const { lineNumber: startLineNumber, column: startColumn } = this._offsetToPosition(relatedResource, info.start); - const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(relatedResource, info.start + info.length); + const result: monaco.editor.IRelatedInformation[] = []; + relatedInformation.forEach((info) => { + let relatedResource: monaco.editor.ITextModel | null = model; + if (info.file) { + const relatedResourceUri = monaco.Uri.parse(info.file.fileName); + relatedResource = monaco.editor.getModel(relatedResourceUri); + } - return { - resource: relatedResource, + if (!relatedResource) { + return; + } + const infoStart = info.start || 0; + const infoLength = info.length || 1; + const { lineNumber: startLineNumber, column: startColumn } = relatedResource.getPositionAt(infoStart); + const { lineNumber: endLineNumber, column: endColumn } = relatedResource.getPositionAt(infoStart + infoLength); + + result.push({ + resource: relatedResource.uri, startLineNumber, startColumn, endLineNumber, endColumn, message: flattenDiagnosticMessageText(info.messageText, '\n') - }; + }); }); + return result; } private _tsDiagnosticCategoryToMarkerSeverity(category: ts.DiagnosticCategory): monaco.MarkerSeverity { @@ -241,6 +254,7 @@ export class DiagnosticsAdapter extends Adapter { case DiagnosticCategory.Warning: return monaco.MarkerSeverity.Warning case DiagnosticCategory.Suggestion: return monaco.MarkerSeverity.Hint } + return monaco.MarkerSeverity.Info; } } @@ -257,68 +271,64 @@ export class SuggestAdapter extends Adapter implements monaco.languages.Completi return ['.']; } - provideCompletionItems(model: monaco.editor.IReadOnlyModel, position: Position, _context: monaco.languages.CompletionContext, token: CancellationToken): Thenable { + public async provideCompletionItems(model: monaco.editor.ITextModel, position: Position, _context: monaco.languages.CompletionContext, token: CancellationToken): Promise { const wordInfo = model.getWordUntilPosition(position); const wordRange = new Range(position.lineNumber, wordInfo.startColumn, position.lineNumber, wordInfo.endColumn); const resource = model.uri; - const offset = this._positionToOffset(resource, position); + const offset = model.getOffsetAt(position); - return this._worker(resource).then(worker => { - return worker.getCompletionsAtPosition(resource.toString(), offset); - }).then(info => { - if (!info) { - return; - } - let suggestions: MyCompletionItem[] = info.entries.map(entry => { - let range = wordRange; - if (entry.replacementSpan) { - const p1 = model.getPositionAt(entry.replacementSpan.start); - const p2 = model.getPositionAt(entry.replacementSpan.start + entry.replacementSpan.length); - range = new Range(p1.lineNumber, p1.column, p2.lineNumber, p2.column); - } + const worker = await this._worker(resource); + const info = await worker.getCompletionsAtPosition(resource.toString(), offset); - return { - uri: resource, - position: position, - range: range, - label: entry.name, - insertText: entry.name, - sortText: entry.sortText, - kind: SuggestAdapter.convertKind(entry.kind) - }; - }); + if (!info || model.isDisposed()) { + return; + } + + const suggestions: MyCompletionItem[] = info.entries.map(entry => { + let range = wordRange; + if (entry.replacementSpan) { + const p1 = model.getPositionAt(entry.replacementSpan.start); + const p2 = model.getPositionAt(entry.replacementSpan.start + entry.replacementSpan.length); + range = new Range(p1.lineNumber, p1.column, p2.lineNumber, p2.column); + } return { - suggestions + uri: resource, + position: position, + range: range, + label: entry.name, + insertText: entry.name, + sortText: entry.sortText, + kind: SuggestAdapter.convertKind(entry.kind) }; }); + + return { + suggestions + }; } - resolveCompletionItem(_model: monaco.editor.IReadOnlyModel, _position: Position, item: monaco.languages.CompletionItem, token: CancellationToken): Thenable { - let myItem = item; + public async resolveCompletionItem(model: monaco.editor.ITextModel, _position: Position, item: monaco.languages.CompletionItem, token: CancellationToken): Promise { + const myItem = item; const resource = myItem.uri; const position = myItem.position; + const offset = model.getOffsetAt(position); - return this._worker(resource).then(worker => { - return worker.getCompletionEntryDetails(resource.toString(), - this._positionToOffset(resource, position), - myItem.label); - - }).then(details => { - if (!details) { - return myItem; + const worker = await this._worker(resource); + const details = await worker.getCompletionEntryDetails(resource.toString(), offset, myItem.label); + if (!details || model.isDisposed()) { + return myItem; + } + return { + uri: resource, + position: position, + label: details.name, + kind: SuggestAdapter.convertKind(details.kind), + detail: displayPartsToString(details.displayParts), + documentation: { + value: displayPartsToString(details.documentation) } - return { - uri: resource, - position: position, - label: details.name, - kind: SuggestAdapter.convertKind(details.kind), - detail: displayPartsToString(details.displayParts), - documentation: { - value: displayPartsToString(details.documentation) - } - }; - }); + }; } private static convertKind(kind: string): monaco.languages.CompletionItemKind { @@ -359,50 +369,51 @@ export class SignatureHelpAdapter extends Adapter implements monaco.languages.Si public signatureHelpTriggerCharacters = ['(', ',']; - provideSignatureHelp(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable { - let resource = model.uri; - return this._worker(resource).then(worker => worker.getSignatureHelpItems(resource.toString(), this._positionToOffset(resource, position))).then(info => { + public async provideSignatureHelp(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise { + const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const info = await worker.getSignatureHelpItems(resource.toString(), offset); - if (!info) { - return; - } + if (!info || model.isDisposed()) { + return; + } - let ret: monaco.languages.SignatureHelp = { - activeSignature: info.selectedItemIndex, - activeParameter: info.argumentIndex, - signatures: [] - }; + const ret: monaco.languages.SignatureHelp = { + activeSignature: info.selectedItemIndex, + activeParameter: info.argumentIndex, + signatures: [] + }; - info.items.forEach(item => { + info.items.forEach(item => { - let signature: monaco.languages.SignatureInformation = { - label: '', - parameters: [] - }; + const signature: monaco.languages.SignatureInformation = { + label: '', + parameters: [] + }; - signature.documentation = displayPartsToString(item.documentation); - signature.label += displayPartsToString(item.prefixDisplayParts); - item.parameters.forEach((p, i, a) => { - let label = displayPartsToString(p.displayParts); - let parameter: monaco.languages.ParameterInformation = { - label: label, - documentation: displayPartsToString(p.documentation) - }; - signature.label += label; - signature.parameters.push(parameter); - if (i < a.length - 1) { - signature.label += displayPartsToString(item.separatorDisplayParts); - } - }); - signature.label += displayPartsToString(item.suffixDisplayParts); - ret.signatures.push(signature); + signature.documentation = displayPartsToString(item.documentation); + signature.label += displayPartsToString(item.prefixDisplayParts); + item.parameters.forEach((p, i, a) => { + const label = displayPartsToString(p.displayParts); + const parameter: monaco.languages.ParameterInformation = { + label: label, + documentation: displayPartsToString(p.documentation) + }; + signature.label += label; + signature.parameters.push(parameter); + if (i < a.length - 1) { + signature.label += displayPartsToString(item.separatorDisplayParts); + } }); - - return { - value: ret, - dispose() { } - }; + signature.label += displayPartsToString(item.suffixDisplayParts); + ret.signatures.push(signature); }); + + return { + value: ret, + dispose() { } + }; } } @@ -410,34 +421,33 @@ export class SignatureHelpAdapter extends Adapter implements monaco.languages.Si export class QuickInfoAdapter extends Adapter implements monaco.languages.HoverProvider { - provideHover(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable { - let resource = model.uri; + public async provideHover(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise { + const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const info = await worker.getQuickInfoAtPosition(resource.toString(), offset); - return this._worker(resource).then(worker => { - return worker.getQuickInfoAtPosition(resource.toString(), this._positionToOffset(resource, position)); - }).then(info => { - if (!info) { - return; + if (!info || model.isDisposed()) { + return; + } + + const documentation = displayPartsToString(info.documentation); + const tags = info.tags ? info.tags.map(tag => { + const label = `*@${tag.name}*`; + if (!tag.text) { + return label; } - let documentation = displayPartsToString(info.documentation); - let tags = info.tags ? info.tags.map(tag => { - const label = `*@${tag.name}*`; - if (!tag.text) { - return label; - } - return label + (tag.text.match(/\r\n|\n/g) ? ' \n' + tag.text : ` - ${tag.text}`); - }) - .join(' \n\n') : ''; - let contents = displayPartsToString(info.displayParts); - return { - range: this._textSpanToRange(resource, info.textSpan), - contents: [{ - value: '```js\n' + contents + '\n```\n' - }, { - value: documentation + (tags ? '\n\n' + tags : '') - }] - }; - }); + return label + (tag.text.match(/\r\n|\n/g) ? ' \n' + tag.text : ` - ${tag.text}`); + }).join(' \n\n') : ''; + const contents = displayPartsToString(info.displayParts); + return { + range: this._textSpanToRange(model, info.textSpan), + contents: [{ + value: '```js\n' + contents + '\n```\n' + }, { + value: documentation + (tags ? '\n\n' + tags : '') + }] + }; } } @@ -445,21 +455,21 @@ export class QuickInfoAdapter extends Adapter implements monaco.languages.HoverP export class OccurrencesAdapter extends Adapter implements monaco.languages.DocumentHighlightProvider { - public provideDocumentHighlights(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable { + public async provideDocumentHighlights(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise { const resource = model.uri; + const offset = model.getOffsetAt(position) + const worker = await this._worker(resource); + const entries = await worker.getOccurrencesAtPosition(resource.toString(), offset); - return this._worker(resource).then(worker => { - return worker.getOccurrencesAtPosition(resource.toString(), this._positionToOffset(resource, position)); - }).then(entries => { - if (!entries) { - return; - } - return entries.map(entry => { - return { - range: this._textSpanToRange(resource, entry.textSpan), - kind: entry.isWriteAccess ? monaco.languages.DocumentHighlightKind.Write : monaco.languages.DocumentHighlightKind.Text - }; - }); + if (!entries || model.isDisposed()) { + return; + } + + return entries.map(entry => { + return { + range: this._textSpanToRange(model, entry.textSpan), + kind: entry.isWriteAccess ? monaco.languages.DocumentHighlightKind.Write : monaco.languages.DocumentHighlightKind.Text + }; }); } } @@ -468,27 +478,28 @@ export class OccurrencesAdapter extends Adapter implements monaco.languages.Docu export class DefinitionAdapter extends Adapter { - public provideDefinition(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable { + public async provideDefinition(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise { const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const entries = await worker.getDefinitionAtPosition(resource.toString(), offset); - return this._worker(resource).then(worker => { - return worker.getDefinitionAtPosition(resource.toString(), this._positionToOffset(resource, position)); - }).then(entries => { - if (!entries) { - return; - } - const result: monaco.languages.Location[] = []; - for (let entry of entries) { - const uri = Uri.parse(entry.fileName); - if (monaco.editor.getModel(uri)) { - result.push({ - uri: uri, - range: this._textSpanToRange(uri, entry.textSpan) - }); - } + if (!entries || model.isDisposed()) { + return; + } + + const result: monaco.languages.Location[] = []; + for (let entry of entries) { + const uri = Uri.parse(entry.fileName); + const refModel = monaco.editor.getModel(uri); + if (refModel) { + result.push({ + uri: uri, + range: this._textSpanToRange(refModel, entry.textSpan) + }); } - return result; - }); + } + return result; } } @@ -496,27 +507,28 @@ export class DefinitionAdapter extends Adapter { export class ReferenceAdapter extends Adapter implements monaco.languages.ReferenceProvider { - provideReferences(model: monaco.editor.IReadOnlyModel, position: Position, context: monaco.languages.ReferenceContext, token: CancellationToken): Thenable { + public async provideReferences(model: monaco.editor.ITextModel, position: Position, context: monaco.languages.ReferenceContext, token: CancellationToken): Promise { const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const entries = await worker.getReferencesAtPosition(resource.toString(), offset); - return this._worker(resource).then(worker => { - return worker.getReferencesAtPosition(resource.toString(), this._positionToOffset(resource, position)); - }).then(entries => { - if (!entries) { - return; - } - const result: monaco.languages.Location[] = []; - for (let entry of entries) { - const uri = Uri.parse(entry.fileName); - if (monaco.editor.getModel(uri)) { - result.push({ - uri: uri, - range: this._textSpanToRange(uri, entry.textSpan) - }); - } + if (!entries || model.isDisposed()) { + return; + } + + const result: monaco.languages.Location[] = []; + for (let entry of entries) { + const uri = Uri.parse(entry.fileName); + const refModel = monaco.editor.getModel(uri); + if (refModel) { + result.push({ + uri: uri, + range: this._textSpanToRange(refModel, entry.textSpan) + }); } - return result; - }); + } + return result; } } @@ -524,38 +536,38 @@ export class ReferenceAdapter extends Adapter implements monaco.languages.Refere export class OutlineAdapter extends Adapter implements monaco.languages.DocumentSymbolProvider { - public provideDocumentSymbols(model: monaco.editor.IReadOnlyModel, token: CancellationToken): Thenable { + public async provideDocumentSymbols(model: monaco.editor.ITextModel, token: CancellationToken): Promise { const resource = model.uri; + const worker = await this._worker(resource); + const items = await worker.getNavigationBarItems(resource.toString()); - return this._worker(resource).then(worker => worker.getNavigationBarItems(resource.toString())).then(items => { - if (!items) { - return; - } + if (!items || model.isDisposed()) { + return; + } - const convert = (bucket: monaco.languages.DocumentSymbol[], item: ts.NavigationBarItem, containerLabel?: string): void => { - let result: monaco.languages.DocumentSymbol = { - name: item.text, - detail: '', - kind: (outlineTypeTable[item.kind] || monaco.languages.SymbolKind.Variable), - range: this._textSpanToRange(resource, item.spans[0]), - selectionRange: this._textSpanToRange(resource, item.spans[0]), - tags: [], - containerName: containerLabel - }; + const convert = (bucket: monaco.languages.DocumentSymbol[], item: ts.NavigationBarItem, containerLabel?: string): void => { + let result: monaco.languages.DocumentSymbol = { + name: item.text, + detail: '', + kind: (outlineTypeTable[item.kind] || monaco.languages.SymbolKind.Variable), + range: this._textSpanToRange(model, item.spans[0]), + selectionRange: this._textSpanToRange(model, item.spans[0]), + tags: [], + containerName: containerLabel + }; - if (item.childItems && item.childItems.length > 0) { - for (let child of item.childItems) { - convert(bucket, child, result.name); - } + if (item.childItems && item.childItems.length > 0) { + for (let child of item.childItems) { + convert(bucket, child, result.name); } - - bucket.push(result); } - let result: monaco.languages.DocumentSymbol[] = []; - items.forEach(item => convert(result, item)); - return result; - }); + bucket.push(result); + } + + let result: monaco.languages.DocumentSymbol[] = []; + items.forEach(item => convert(result, item)); + return result; } } @@ -629,29 +641,28 @@ export abstract class FormatHelper extends Adapter { }; } - protected _convertTextChanges(uri: Uri, change: ts.TextChange): monaco.editor.ISingleEditOperation { - return { + protected _convertTextChanges(model: monaco.editor.ITextModel, change: ts.TextChange): monaco.languages.TextEdit { + return { text: change.newText, - range: this._textSpanToRange(uri, change.span) + range: this._textSpanToRange(model, change.span) }; } } export class FormatAdapter extends FormatHelper implements monaco.languages.DocumentRangeFormattingEditProvider { - provideDocumentRangeFormattingEdits(model: monaco.editor.IReadOnlyModel, range: Range, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable { + public async provideDocumentRangeFormattingEdits(model: monaco.editor.ITextModel, range: Range, options: monaco.languages.FormattingOptions, token: CancellationToken): Promise { const resource = model.uri; + const startOffset = model.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn }); + const endOffset = model.getOffsetAt({ lineNumber: range.endLineNumber, column: range.endColumn }); + const worker = await this._worker(resource); + const edits = await worker.getFormattingEditsForRange(resource.toString(), startOffset, endOffset, FormatHelper._convertOptions(options)); - return this._worker(resource).then(worker => { - return worker.getFormattingEditsForRange(resource.toString(), - this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn }), - this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn }), - FormatHelper._convertOptions(options)); - }).then(edits => { - if (edits) { - return edits.map(edit => this._convertTextChanges(resource, edit)); - } - }); + if (!edits || model.isDisposed()) { + return; + } + + return edits.map(edit => this._convertTextChanges(model, edit)); } } @@ -661,18 +672,17 @@ export class FormatOnTypeAdapter extends FormatHelper implements monaco.language return [';', '}', '\n']; } - provideOnTypeFormattingEdits(model: monaco.editor.IReadOnlyModel, position: Position, ch: string, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable { + public async provideOnTypeFormattingEdits(model: monaco.editor.ITextModel, position: Position, ch: string, options: monaco.languages.FormattingOptions, token: CancellationToken): Promise { const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const edits = await worker.getFormattingEditsAfterKeystroke(resource.toString(), offset, ch, FormatHelper._convertOptions(options)); - return this._worker(resource).then(worker => { - return worker.getFormattingEditsAfterKeystroke(resource.toString(), - this._positionToOffset(resource, position), - ch, FormatHelper._convertOptions(options)); - }).then(edits => { - if (edits) { - return edits.map(edit => this._convertTextChanges(resource, edit)); - } - }); + if (!edits || model.isDisposed()) { + return; + } + + return edits.map(edit => this._convertTextChanges(model, edit)); } } @@ -680,32 +690,30 @@ export class FormatOnTypeAdapter extends FormatHelper implements monaco.language export class CodeActionAdaptor extends FormatHelper implements monaco.languages.CodeActionProvider { - public provideCodeActions(model: monaco.editor.ITextModel, range: Range, context: monaco.languages.CodeActionContext, token: CancellationToken): Promise { + public async provideCodeActions(model: monaco.editor.ITextModel, range: Range, context: monaco.languages.CodeActionContext, token: CancellationToken): Promise { const resource = model.uri; + const start = model.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn }); + const end = model.getOffsetAt({ lineNumber: range.endLineNumber, column: range.endColumn }); + const formatOptions = FormatHelper._convertOptions(model.getOptions()); + const errorCodes = context.markers.filter(m => m.code).map(m => m.code).map(Number); + const worker = await this._worker(resource); + const codeFixes = await worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions); - return this._worker(resource).then(worker => { - const start = this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn }); - const end = this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn }); - - const formatOptions = FormatHelper._convertOptions(model.getOptions()); - const errorCodes = context.markers.filter(m => m.code).map(m => m.code).map(Number); - - return worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions); - - }).then(codeFixes => { + if (!codeFixes || model.isDisposed()) { + return; + } - return codeFixes.filter(fix => { - // Removes any 'make a new file'-type code fix - return fix.changes.filter(change => change.isNewFile).length === 0; - }).map(fix => { - return this._tsCodeFixActionToMonacoCodeAction(model, context, fix); - }) - }).then(result => { - return { - actions: result, - dispose: () => { } - }; + const actions = codeFixes.filter(fix => { + // Removes any 'make a new file'-type code fix + return fix.changes.filter(change => change.isNewFile).length === 0; + }).map(fix => { + return this._tsCodeFixActionToMonacoCodeAction(model, context, fix); }); + + return { + actions: actions, + dispose: () => { } + }; } @@ -713,7 +721,7 @@ export class CodeActionAdaptor extends FormatHelper implements monaco.languages. const edits: monaco.languages.ResourceTextEdit[] = codeFix.changes.map(edit => ({ resource: model.uri, edits: edit.textChanges.map(tc => ({ - range: this._textSpanToRange(model.uri, tc.span), + range: this._textSpanToRange(model, tc.span), text: tc.newText })) })); @@ -732,10 +740,10 @@ export class CodeActionAdaptor extends FormatHelper implements monaco.languages. export class RenameAdapter extends Adapter implements monaco.languages.RenameProvider { - async provideRenameEdits(model: monaco.editor.ITextModel, position: Position, newName: string, token: CancellationToken): Promise { + public async provideRenameEdits(model: monaco.editor.ITextModel, position: Position, newName: string, token: CancellationToken): Promise { const resource = model.uri; const fileName = resource.toString(); - const offset = this._positionToOffset(resource, position); + const offset = model.getOffsetAt(position); const worker = await this._worker(resource); const renameInfo = await worker.getRenameInfo(fileName, offset, { allowRenameOfImportPath: false }); @@ -750,6 +758,11 @@ export class RenameAdapter extends Adapter implements monaco.languages.RenamePro } const renameLocations = await worker.findRenameLocations(fileName, offset, /*strings*/ false, /*comments*/ false, /*prefixAndSuffix*/ false); + + if (!renameLocations || model.isDisposed()) { + return; + } + const fileNameToResourceTextEditMap: { [fileName: string]: monaco.languages.ResourceTextEdit } = {}; const edits: monaco.languages.ResourceTextEdit[] = []; @@ -764,7 +777,7 @@ export class RenameAdapter extends Adapter implements monaco.languages.RenamePro } fileNameToResourceTextEditMap[renameLocation.fileName].edits.push({ - range: this._textSpanToRange(resource, renameLocation.textSpan), + range: this._textSpanToRange(model, renameLocation.textSpan), text: newName }); } diff --git a/src/lib/editor.worker.d.ts b/src/lib/editor.worker.d.ts new file mode 100644 index 00000000..9481c112 --- /dev/null +++ b/src/lib/editor.worker.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'monaco-editor-core/esm/vs/editor/editor.worker' { + export function initialize(callback: (ctx: monaco.worker.IWorkerContext, createData: any) => any): void; +} diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 69c3530e..cfab1a62 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -30,13 +30,14 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. private _extraLibs: IExtraLibs; private _workerMaxIdleTime: number; private _eagerModelSync: boolean; - private _compilerOptions: monaco.languages.typescript.CompilerOptions; - private _diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions; + private _compilerOptions!: monaco.languages.typescript.CompilerOptions; + private _diagnosticsOptions!: monaco.languages.typescript.DiagnosticsOptions; private _onDidExtraLibsChangeTimeout: number; constructor(compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions) { this._extraLibs = Object.create(null); this._workerMaxIdleTime = 2 * 60 * 1000; + this._eagerModelSync = false; this.setCompilerOptions(compilerOptions); this.setDiagnosticsOptions(diagnosticsOptions); this._onDidExtraLibsChangeTimeout = -1; @@ -54,9 +55,12 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. return this._extraLibs; } - addExtraLib(content: string, filePath?: string): IDisposable { - if (typeof filePath === 'undefined') { + addExtraLib(content: string, _filePath?: string): IDisposable { + let filePath: string; + if (typeof _filePath === 'undefined') { filePath = `ts:extralib-${Math.random().toString(36).substring(2, 15)}`; + } else { + filePath = _filePath; } if (this._extraLibs[filePath] && this._extraLibs[filePath].content === content) { diff --git a/src/ts.worker.ts b/src/ts.worker.ts index b55939f2..880b083f 100644 --- a/src/ts.worker.ts +++ b/src/ts.worker.ts @@ -5,11 +5,11 @@ 'use strict'; import * as worker from 'monaco-editor-core/esm/vs/editor/editor.worker'; -import { TypeScriptWorker } from './tsWorker'; +import { TypeScriptWorker, ICreateData } from './tsWorker'; self.onmessage = () => { // ignore the first message - worker.initialize((ctx, createData) => { + worker.initialize((ctx: monaco.worker.IWorkerContext, createData: ICreateData) => { return new TypeScriptWorker(ctx, createData) }); }; diff --git a/src/tsWorker.ts b/src/tsWorker.ts index 06787de2..b3cee653 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -46,7 +46,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { return models.concat(Object.keys(this._extraLibs)); } - private _getModel(fileName: string): monaco.worker.IMirrorModel { + private _getModel(fileName: string): monaco.worker.IMirrorModel | null { let models = this._ctx.getMirrorModels(); for (let i = 0; i < models.length; i++) { if (models[i].uri.toString() === fileName) { @@ -66,9 +66,10 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { } else if (fileName in this._extraLibs) { return String(this._extraLibs[fileName].version); } + return ''; } - getScriptSnapshot(fileName: string): ts.IScriptSnapshot { + getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { let text: string; let model = this._getModel(fileName); if (model) { @@ -113,7 +114,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { getDefaultLibFileName(options: ts.CompilerOptions): string { // TODO@joh support lib.es7.d.ts - return options.target <= ts.ScriptTarget.ES5 ? DEFAULT_LIB.NAME : ES6_LIB.NAME; + return (options.target || ts.ScriptTarget.ES5) <= ts.ScriptTarget.ES5 ? DEFAULT_LIB.NAME : ES6_LIB.NAME; } isDefaultLibFileName(fileName: string): boolean { @@ -158,31 +159,31 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { return Promise.resolve(diagnostics); } - getCompletionsAtPosition(fileName: string, position: number): Promise { + getCompletionsAtPosition(fileName: string, position: number): Promise { return Promise.resolve(this._languageService.getCompletionsAtPosition(fileName, position, undefined)); } - getCompletionEntryDetails(fileName: string, position: number, entry: string): Promise { + getCompletionEntryDetails(fileName: string, position: number, entry: string): Promise { return Promise.resolve(this._languageService.getCompletionEntryDetails(fileName, position, entry, undefined, undefined, undefined)); } - getSignatureHelpItems(fileName: string, position: number): Promise { + getSignatureHelpItems(fileName: string, position: number): Promise { return Promise.resolve(this._languageService.getSignatureHelpItems(fileName, position, undefined)); } - getQuickInfoAtPosition(fileName: string, position: number): Promise { + getQuickInfoAtPosition(fileName: string, position: number): Promise { return Promise.resolve(this._languageService.getQuickInfoAtPosition(fileName, position)); } - getOccurrencesAtPosition(fileName: string, position: number): Promise> { + getOccurrencesAtPosition(fileName: string, position: number): Promise | undefined> { return Promise.resolve(this._languageService.getOccurrencesAtPosition(fileName, position)); } - getDefinitionAtPosition(fileName: string, position: number): Promise> { + getDefinitionAtPosition(fileName: string, position: number): Promise | undefined> { return Promise.resolve(this._languageService.getDefinitionAtPosition(fileName, position)); } - getReferencesAtPosition(fileName: string, position: number): Promise { + getReferencesAtPosition(fileName: string, position: number): Promise { return Promise.resolve(this._languageService.getReferencesAtPosition(fileName, position)); } @@ -202,7 +203,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { return Promise.resolve(this._languageService.getFormattingEditsAfterKeystroke(fileName, postion, ch, options)); } - findRenameLocations(fileName: string, positon: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename: boolean): Promise { + findRenameLocations(fileName: string, positon: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename: boolean): Promise { return Promise.resolve(this._languageService.findRenameLocations(fileName, positon, findInStrings, findInComments, providePrefixAndSuffixTextForRename)); } diff --git a/src/tsconfig.esm.json b/src/tsconfig.esm.json index d6085367..54e3e2cf 100644 --- a/src/tsconfig.esm.json +++ b/src/tsconfig.esm.json @@ -11,7 +11,8 @@ "es2015.collection", "es2015.iterable", "es2015.promise" - ] + ], + "strict": true }, "include": [ "**/*.ts" @@ -19,4 +20,4 @@ "files": [ "../node_modules/monaco-editor-core/monaco.d.ts" ] -} \ No newline at end of file +} diff --git a/src/tsconfig.json b/src/tsconfig.json index 2dbccb11..2dfb4517 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -11,7 +11,8 @@ "es2015.collection", "es2015.iterable", "es2015.promise" - ] + ], + "strict": true, }, "include": [ "**/*.ts" @@ -19,4 +20,4 @@ "files": [ "../node_modules/monaco-editor-core/monaco.d.ts" ] -} \ No newline at end of file +} diff --git a/src/workerManager.ts b/src/workerManager.ts index 666c9fc9..07254338 100644 --- a/src/workerManager.ts +++ b/src/workerManager.ts @@ -20,13 +20,14 @@ export class WorkerManager { private _updateExtraLibsToken: number; private _extraLibsChangeListener: IDisposable; - private _worker: monaco.editor.MonacoWebWorker; - private _client: Promise; + private _worker: monaco.editor.MonacoWebWorker | null; + private _client: Promise | null; constructor(modeId: string, defaults: LanguageServiceDefaultsImpl) { this._modeId = modeId; this._defaults = defaults; this._worker = null; + this._client = null; this._idleCheckInterval = setInterval(() => this._checkIfIdle(), 30 * 1000); this._lastUsedTime = 0; this._configChangeListener = this._defaults.onDidChange(() => this._stopWorker()); @@ -95,11 +96,14 @@ export class WorkerManager { if (this._defaults.getEagerModelSync()) { p = p.then(worker => { - return this._worker.withSyncedResources(monaco.editor.getModels() - .filter(model => model.getModeId() === this._modeId) - .map(model => model.uri) - ); - }) + if (this._worker) { + return this._worker.withSyncedResources(monaco.editor.getModels() + .filter(model => model.getModeId() === this._modeId) + .map(model => model.uri) + ); + } + return worker; + }); } this._client = p; @@ -113,7 +117,9 @@ export class WorkerManager { return this._getClient().then((client) => { _client = client }).then(_ => { - return this._worker.withSyncedResources(resources) + if (this._worker) { + return this._worker.withSyncedResources(resources) + } }).then(_ => _client); } }