languageFeatures.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  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. import { LanguageServiceDefaultsImpl } from './monaco.contribution';
  7. import * as ts from './lib/typescriptServices';
  8. import { TypeScriptWorker } from './tsWorker';
  9. import Uri = monaco.Uri;
  10. import Position = monaco.Position;
  11. import Range = monaco.Range;
  12. import Thenable = monaco.Thenable;
  13. import CancellationToken = monaco.CancellationToken;
  14. import IDisposable = monaco.IDisposable;
  15. //#region utils copied from typescript to prevent loading the entire typescriptServices ---
  16. enum IndentStyle {
  17. None = 0,
  18. Block = 1,
  19. Smart = 2
  20. }
  21. export function flattenDiagnosticMessageText(diag: string | ts.DiagnosticMessageChain | undefined, newLine: string, indent = 0): string {
  22. if (typeof diag === "string") {
  23. return diag;
  24. }
  25. else if (diag === undefined) {
  26. return "";
  27. }
  28. let result = "";
  29. if (indent) {
  30. result += newLine;
  31. for (let i = 0; i < indent; i++) {
  32. result += " ";
  33. }
  34. }
  35. result += diag.messageText;
  36. indent++;
  37. if (diag.next) {
  38. for (const kid of diag.next) {
  39. result += flattenDiagnosticMessageText(kid, newLine, indent);
  40. }
  41. }
  42. return result;
  43. }
  44. function displayPartsToString(displayParts: ts.SymbolDisplayPart[]): string {
  45. if (displayParts) {
  46. return displayParts.map((displayPart) => displayPart.text).join("");
  47. }
  48. return "";
  49. }
  50. //#endregion
  51. export abstract class Adapter {
  52. constructor(protected _worker: (first: Uri, ...more: Uri[]) => Promise<TypeScriptWorker>) {
  53. }
  54. protected _positionToOffset(uri: Uri, position: monaco.IPosition): number {
  55. let model = monaco.editor.getModel(uri);
  56. return model.getOffsetAt(position);
  57. }
  58. protected _offsetToPosition(uri: Uri, offset: number): monaco.IPosition {
  59. let model = monaco.editor.getModel(uri);
  60. return model.getPositionAt(offset);
  61. }
  62. protected _textSpanToRange(uri: Uri, span: ts.TextSpan): monaco.IRange {
  63. let p1 = this._offsetToPosition(uri, span.start);
  64. let p2 = this._offsetToPosition(uri, span.start + span.length);
  65. let { lineNumber: startLineNumber, column: startColumn } = p1;
  66. let { lineNumber: endLineNumber, column: endColumn } = p2;
  67. return { startLineNumber, startColumn, endLineNumber, endColumn };
  68. }
  69. }
  70. // --- diagnostics --- ---
  71. export class DiagnosticsAdapter extends Adapter {
  72. private _disposables: IDisposable[] = [];
  73. private _listener: { [uri: string]: IDisposable } = Object.create(null);
  74. constructor(private _defaults: LanguageServiceDefaultsImpl, private _selector: string,
  75. worker: (first: Uri, ...more: Uri[]) => Promise<TypeScriptWorker>
  76. ) {
  77. super(worker);
  78. const onModelAdd = (model: monaco.editor.IModel): void => {
  79. if (model.getModeId() !== _selector) {
  80. return;
  81. }
  82. let handle: number;
  83. const changeSubscription = model.onDidChangeContent(() => {
  84. clearTimeout(handle);
  85. handle = setTimeout(() => this._doValidate(model.uri), 500);
  86. });
  87. this._listener[model.uri.toString()] = {
  88. dispose() {
  89. changeSubscription.dispose();
  90. clearTimeout(handle);
  91. }
  92. };
  93. this._doValidate(model.uri);
  94. };
  95. const onModelRemoved = (model: monaco.editor.IModel): void => {
  96. monaco.editor.setModelMarkers(model, this._selector, []);
  97. const key = model.uri.toString();
  98. if (this._listener[key]) {
  99. this._listener[key].dispose();
  100. delete this._listener[key];
  101. }
  102. };
  103. this._disposables.push(monaco.editor.onDidCreateModel(onModelAdd));
  104. this._disposables.push(monaco.editor.onWillDisposeModel(onModelRemoved));
  105. this._disposables.push(monaco.editor.onDidChangeModelLanguage(event => {
  106. onModelRemoved(event.model);
  107. onModelAdd(event.model);
  108. }));
  109. this._disposables.push({
  110. dispose() {
  111. for (const model of monaco.editor.getModels()) {
  112. onModelRemoved(model);
  113. }
  114. }
  115. });
  116. const recomputeDiagostics = () => {
  117. // redo diagnostics when options change
  118. for (const model of monaco.editor.getModels()) {
  119. onModelRemoved(model);
  120. onModelAdd(model);
  121. }
  122. };
  123. this._disposables.push(this._defaults.onDidChange(recomputeDiagostics));
  124. this._disposables.push(this._defaults.onDidExtraLibsChange(recomputeDiagostics));
  125. monaco.editor.getModels().forEach(onModelAdd);
  126. }
  127. public dispose(): void {
  128. this._disposables.forEach(d => d && d.dispose());
  129. this._disposables = [];
  130. }
  131. private _doValidate(resource: Uri): void {
  132. this._worker(resource).then(worker => {
  133. if (!monaco.editor.getModel(resource)) {
  134. // model was disposed in the meantime
  135. return null;
  136. }
  137. const promises: Promise<ts.Diagnostic[]>[] = [];
  138. const { noSyntaxValidation, noSemanticValidation, noSuggestionDiagnostics } = this._defaults.getDiagnosticsOptions();
  139. if (!noSyntaxValidation) {
  140. promises.push(worker.getSyntacticDiagnostics(resource.toString()));
  141. }
  142. if (!noSemanticValidation) {
  143. promises.push(worker.getSemanticDiagnostics(resource.toString()));
  144. }
  145. if (!noSuggestionDiagnostics) {
  146. promises.push(worker.getSuggestionDiagnostics(resource.toString()));
  147. }
  148. return Promise.all(promises);
  149. }).then(diagnostics => {
  150. if (!diagnostics || !monaco.editor.getModel(resource)) {
  151. // model was disposed in the meantime
  152. return null;
  153. }
  154. const markers = diagnostics
  155. .reduce((p, c) => c.concat(p), [])
  156. .filter(d => (this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore || []).indexOf(d.code) === -1)
  157. .map(d => this._convertDiagnostics(resource, d));
  158. monaco.editor.setModelMarkers(monaco.editor.getModel(resource), this._selector, markers);
  159. }).then(undefined, err => {
  160. console.error(err);
  161. });
  162. }
  163. private _convertDiagnostics(resource: Uri, diag: ts.Diagnostic): monaco.editor.IMarkerData {
  164. const { lineNumber: startLineNumber, column: startColumn } = this._offsetToPosition(resource, diag.start);
  165. const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(resource, diag.start + diag.length);
  166. return {
  167. severity: this._tsDiagnosticCategoryToMarkerSeverity(diag.category),
  168. startLineNumber,
  169. startColumn,
  170. endLineNumber,
  171. endColumn,
  172. message: flattenDiagnosticMessageText(diag.messageText, '\n'),
  173. code: diag.code.toString(),
  174. tags: diag.reportsUnnecessary ? [monaco.MarkerTag.Unnecessary] : [],
  175. relatedInformation: this._convertRelatedInformation(resource, diag.relatedInformation),
  176. };
  177. }
  178. private _convertRelatedInformation(resource: Uri, relatedInformation?: ts.DiagnosticRelatedInformation[]): monaco.editor.IRelatedInformation[] {
  179. if (relatedInformation === undefined)
  180. return undefined;
  181. return relatedInformation.map(info => {
  182. const relatedResource = info.file === undefined ? resource : monaco.Uri.parse(info.file.fileName);
  183. const { lineNumber: startLineNumber, column: startColumn } = this._offsetToPosition(relatedResource, info.start);
  184. const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(relatedResource, info.start + info.length);
  185. return {
  186. resource: relatedResource,
  187. startLineNumber,
  188. startColumn,
  189. endLineNumber,
  190. endColumn,
  191. message: flattenDiagnosticMessageText(info.messageText, '\n')
  192. };
  193. });
  194. }
  195. private _tsDiagnosticCategoryToMarkerSeverity(category: ts.DiagnosticCategory): monaco.MarkerSeverity {
  196. switch (category) {
  197. case ts.DiagnosticCategory.Error: return monaco.MarkerSeverity.Error
  198. case ts.DiagnosticCategory.Message: return monaco.MarkerSeverity.Info
  199. case ts.DiagnosticCategory.Warning: return monaco.MarkerSeverity.Warning
  200. case ts.DiagnosticCategory.Suggestion: return monaco.MarkerSeverity.Hint
  201. }
  202. }
  203. }
  204. // --- suggest ------
  205. interface MyCompletionItem extends monaco.languages.CompletionItem {
  206. uri: Uri;
  207. position: Position;
  208. }
  209. export class SuggestAdapter extends Adapter implements monaco.languages.CompletionItemProvider {
  210. public get triggerCharacters(): string[] {
  211. return ['.'];
  212. }
  213. provideCompletionItems(model: monaco.editor.IReadOnlyModel, position: Position, _context: monaco.languages.CompletionContext, token: CancellationToken): Thenable<monaco.languages.CompletionList> {
  214. const wordInfo = model.getWordUntilPosition(position);
  215. const wordRange = new Range(position.lineNumber, wordInfo.startColumn, position.lineNumber, wordInfo.endColumn);
  216. const resource = model.uri;
  217. const offset = this._positionToOffset(resource, position);
  218. return this._worker(resource).then(worker => {
  219. return worker.getCompletionsAtPosition(resource.toString(), offset);
  220. }).then(info => {
  221. if (!info) {
  222. return;
  223. }
  224. let suggestions: MyCompletionItem[] = info.entries.map(entry => {
  225. let range = wordRange;
  226. if (entry.replacementSpan) {
  227. const p1 = model.getPositionAt(entry.replacementSpan.start);
  228. const p2 = model.getPositionAt(entry.replacementSpan.start + entry.replacementSpan.length);
  229. range = new Range(p1.lineNumber, p1.column, p2.lineNumber, p2.column);
  230. }
  231. return {
  232. uri: resource,
  233. position: position,
  234. range: range,
  235. label: entry.name,
  236. insertText: entry.name,
  237. sortText: entry.sortText,
  238. kind: SuggestAdapter.convertKind(entry.kind)
  239. };
  240. });
  241. return {
  242. suggestions
  243. };
  244. });
  245. }
  246. resolveCompletionItem(_model: monaco.editor.IReadOnlyModel, _position: Position, item: monaco.languages.CompletionItem, token: CancellationToken): Thenable<monaco.languages.CompletionItem> {
  247. let myItem = <MyCompletionItem>item;
  248. const resource = myItem.uri;
  249. const position = myItem.position;
  250. return this._worker(resource).then(worker => {
  251. return worker.getCompletionEntryDetails(resource.toString(),
  252. this._positionToOffset(resource, position),
  253. myItem.label);
  254. }).then(details => {
  255. if (!details) {
  256. return myItem;
  257. }
  258. return <MyCompletionItem>{
  259. uri: resource,
  260. position: position,
  261. label: details.name,
  262. kind: SuggestAdapter.convertKind(details.kind),
  263. detail: displayPartsToString(details.displayParts),
  264. documentation: {
  265. value: displayPartsToString(details.documentation)
  266. }
  267. };
  268. });
  269. }
  270. private static convertKind(kind: string): monaco.languages.CompletionItemKind {
  271. switch (kind) {
  272. case Kind.primitiveType:
  273. case Kind.keyword:
  274. return monaco.languages.CompletionItemKind.Keyword;
  275. case Kind.variable:
  276. case Kind.localVariable:
  277. return monaco.languages.CompletionItemKind.Variable;
  278. case Kind.memberVariable:
  279. case Kind.memberGetAccessor:
  280. case Kind.memberSetAccessor:
  281. return monaco.languages.CompletionItemKind.Field;
  282. case Kind.function:
  283. case Kind.memberFunction:
  284. case Kind.constructSignature:
  285. case Kind.callSignature:
  286. case Kind.indexSignature:
  287. return monaco.languages.CompletionItemKind.Function;
  288. case Kind.enum:
  289. return monaco.languages.CompletionItemKind.Enum;
  290. case Kind.module:
  291. return monaco.languages.CompletionItemKind.Module;
  292. case Kind.class:
  293. return monaco.languages.CompletionItemKind.Class;
  294. case Kind.interface:
  295. return monaco.languages.CompletionItemKind.Interface;
  296. case Kind.warning:
  297. return monaco.languages.CompletionItemKind.File;
  298. }
  299. return monaco.languages.CompletionItemKind.Property;
  300. }
  301. }
  302. export class SignatureHelpAdapter extends Adapter implements monaco.languages.SignatureHelpProvider {
  303. public signatureHelpTriggerCharacters = ['(', ','];
  304. provideSignatureHelp(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.SignatureHelpResult> {
  305. let resource = model.uri;
  306. return this._worker(resource).then(worker => worker.getSignatureHelpItems(resource.toString(), this._positionToOffset(resource, position))).then(info => {
  307. if (!info) {
  308. return;
  309. }
  310. let ret: monaco.languages.SignatureHelp = {
  311. activeSignature: info.selectedItemIndex,
  312. activeParameter: info.argumentIndex,
  313. signatures: []
  314. };
  315. info.items.forEach(item => {
  316. let signature: monaco.languages.SignatureInformation = {
  317. label: '',
  318. parameters: []
  319. };
  320. signature.label += displayPartsToString(item.prefixDisplayParts);
  321. item.parameters.forEach((p, i, a) => {
  322. let label = displayPartsToString(p.displayParts);
  323. let parameter: monaco.languages.ParameterInformation = {
  324. label: label,
  325. documentation: displayPartsToString(p.documentation)
  326. };
  327. signature.label += label;
  328. signature.parameters.push(parameter);
  329. if (i < a.length - 1) {
  330. signature.label += displayPartsToString(item.separatorDisplayParts);
  331. }
  332. });
  333. signature.label += displayPartsToString(item.suffixDisplayParts);
  334. ret.signatures.push(signature);
  335. });
  336. return {
  337. value: ret,
  338. dispose() { }
  339. };
  340. });
  341. }
  342. }
  343. // --- hover ------
  344. export class QuickInfoAdapter extends Adapter implements monaco.languages.HoverProvider {
  345. provideHover(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.Hover> {
  346. let resource = model.uri;
  347. return this._worker(resource).then(worker => {
  348. return worker.getQuickInfoAtPosition(resource.toString(), this._positionToOffset(resource, position));
  349. }).then(info => {
  350. if (!info) {
  351. return;
  352. }
  353. let documentation = displayPartsToString(info.documentation);
  354. let tags = info.tags ? info.tags.map(tag => {
  355. const label = `*@${tag.name}*`;
  356. if (!tag.text) {
  357. return label;
  358. }
  359. return label + (tag.text.match(/\r\n|\n/g) ? ' \n' + tag.text : ` - ${tag.text}`);
  360. })
  361. .join(' \n\n') : '';
  362. let contents = displayPartsToString(info.displayParts);
  363. return {
  364. range: this._textSpanToRange(resource, info.textSpan),
  365. contents: [{
  366. value: '```js\n' + contents + '\n```\n'
  367. }, {
  368. value: documentation + (tags ? '\n\n' + tags : '')
  369. }]
  370. };
  371. });
  372. }
  373. }
  374. // --- occurrences ------
  375. export class OccurrencesAdapter extends Adapter implements monaco.languages.DocumentHighlightProvider {
  376. public provideDocumentHighlights(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.DocumentHighlight[]> {
  377. const resource = model.uri;
  378. return this._worker(resource).then(worker => {
  379. return worker.getOccurrencesAtPosition(resource.toString(), this._positionToOffset(resource, position));
  380. }).then(entries => {
  381. if (!entries) {
  382. return;
  383. }
  384. return entries.map(entry => {
  385. return <monaco.languages.DocumentHighlight>{
  386. range: this._textSpanToRange(resource, entry.textSpan),
  387. kind: entry.isWriteAccess ? monaco.languages.DocumentHighlightKind.Write : monaco.languages.DocumentHighlightKind.Text
  388. };
  389. });
  390. });
  391. }
  392. }
  393. // --- definition ------
  394. export class DefinitionAdapter extends Adapter {
  395. public provideDefinition(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.Definition> {
  396. const resource = model.uri;
  397. return this._worker(resource).then(worker => {
  398. return worker.getDefinitionAtPosition(resource.toString(), this._positionToOffset(resource, position));
  399. }).then(entries => {
  400. if (!entries) {
  401. return;
  402. }
  403. const result: monaco.languages.Location[] = [];
  404. for (let entry of entries) {
  405. const uri = Uri.parse(entry.fileName);
  406. if (monaco.editor.getModel(uri)) {
  407. result.push({
  408. uri: uri,
  409. range: this._textSpanToRange(uri, entry.textSpan)
  410. });
  411. }
  412. }
  413. return result;
  414. });
  415. }
  416. }
  417. // --- references ------
  418. export class ReferenceAdapter extends Adapter implements monaco.languages.ReferenceProvider {
  419. provideReferences(model: monaco.editor.IReadOnlyModel, position: Position, context: monaco.languages.ReferenceContext, token: CancellationToken): Thenable<monaco.languages.Location[]> {
  420. const resource = model.uri;
  421. return this._worker(resource).then(worker => {
  422. return worker.getReferencesAtPosition(resource.toString(), this._positionToOffset(resource, position));
  423. }).then(entries => {
  424. if (!entries) {
  425. return;
  426. }
  427. const result: monaco.languages.Location[] = [];
  428. for (let entry of entries) {
  429. const uri = Uri.parse(entry.fileName);
  430. if (monaco.editor.getModel(uri)) {
  431. result.push({
  432. uri: uri,
  433. range: this._textSpanToRange(uri, entry.textSpan)
  434. });
  435. }
  436. }
  437. return result;
  438. });
  439. }
  440. }
  441. // --- outline ------
  442. export class OutlineAdapter extends Adapter implements monaco.languages.DocumentSymbolProvider {
  443. public provideDocumentSymbols(model: monaco.editor.IReadOnlyModel, token: CancellationToken): Thenable<monaco.languages.DocumentSymbol[]> {
  444. const resource = model.uri;
  445. return this._worker(resource).then(worker => worker.getNavigationBarItems(resource.toString())).then(items => {
  446. if (!items) {
  447. return;
  448. }
  449. const convert = (bucket: monaco.languages.DocumentSymbol[], item: ts.NavigationBarItem, containerLabel?: string): void => {
  450. let result: monaco.languages.DocumentSymbol = {
  451. name: item.text,
  452. detail: '',
  453. kind: <monaco.languages.SymbolKind>(outlineTypeTable[item.kind] || monaco.languages.SymbolKind.Variable),
  454. range: this._textSpanToRange(resource, item.spans[0]),
  455. selectionRange: this._textSpanToRange(resource, item.spans[0]),
  456. tags: [],
  457. containerName: containerLabel
  458. };
  459. if (item.childItems && item.childItems.length > 0) {
  460. for (let child of item.childItems) {
  461. convert(bucket, child, result.name);
  462. }
  463. }
  464. bucket.push(result);
  465. }
  466. let result: monaco.languages.DocumentSymbol[] = [];
  467. items.forEach(item => convert(result, item));
  468. return result;
  469. });
  470. }
  471. }
  472. export class Kind {
  473. public static unknown: string = '';
  474. public static keyword: string = 'keyword';
  475. public static script: string = 'script';
  476. public static module: string = 'module';
  477. public static class: string = 'class';
  478. public static interface: string = 'interface';
  479. public static type: string = 'type';
  480. public static enum: string = 'enum';
  481. public static variable: string = 'var';
  482. public static localVariable: string = 'local var';
  483. public static function: string = 'function';
  484. public static localFunction: string = 'local function';
  485. public static memberFunction: string = 'method';
  486. public static memberGetAccessor: string = 'getter';
  487. public static memberSetAccessor: string = 'setter';
  488. public static memberVariable: string = 'property';
  489. public static constructorImplementation: string = 'constructor';
  490. public static callSignature: string = 'call';
  491. public static indexSignature: string = 'index';
  492. public static constructSignature: string = 'construct';
  493. public static parameter: string = 'parameter';
  494. public static typeParameter: string = 'type parameter';
  495. public static primitiveType: string = 'primitive type';
  496. public static label: string = 'label';
  497. public static alias: string = 'alias';
  498. public static const: string = 'const';
  499. public static let: string = 'let';
  500. public static warning: string = 'warning';
  501. }
  502. let outlineTypeTable: { [kind: string]: monaco.languages.SymbolKind } = Object.create(null);
  503. outlineTypeTable[Kind.module] = monaco.languages.SymbolKind.Module;
  504. outlineTypeTable[Kind.class] = monaco.languages.SymbolKind.Class;
  505. outlineTypeTable[Kind.enum] = monaco.languages.SymbolKind.Enum;
  506. outlineTypeTable[Kind.interface] = monaco.languages.SymbolKind.Interface;
  507. outlineTypeTable[Kind.memberFunction] = monaco.languages.SymbolKind.Method;
  508. outlineTypeTable[Kind.memberVariable] = monaco.languages.SymbolKind.Property;
  509. outlineTypeTable[Kind.memberGetAccessor] = monaco.languages.SymbolKind.Property;
  510. outlineTypeTable[Kind.memberSetAccessor] = monaco.languages.SymbolKind.Property;
  511. outlineTypeTable[Kind.variable] = monaco.languages.SymbolKind.Variable;
  512. outlineTypeTable[Kind.const] = monaco.languages.SymbolKind.Variable;
  513. outlineTypeTable[Kind.localVariable] = monaco.languages.SymbolKind.Variable;
  514. outlineTypeTable[Kind.variable] = monaco.languages.SymbolKind.Variable;
  515. outlineTypeTable[Kind.function] = monaco.languages.SymbolKind.Function;
  516. outlineTypeTable[Kind.localFunction] = monaco.languages.SymbolKind.Function;
  517. // --- formatting ----
  518. export abstract class FormatHelper extends Adapter {
  519. protected static _convertOptions(options: monaco.languages.FormattingOptions): ts.FormatCodeOptions {
  520. return {
  521. ConvertTabsToSpaces: options.insertSpaces,
  522. TabSize: options.tabSize,
  523. IndentSize: options.tabSize,
  524. IndentStyle: IndentStyle.Smart,
  525. NewLineCharacter: '\n',
  526. InsertSpaceAfterCommaDelimiter: true,
  527. InsertSpaceAfterSemicolonInForStatements: true,
  528. InsertSpaceBeforeAndAfterBinaryOperators: true,
  529. InsertSpaceAfterKeywordsInControlFlowStatements: true,
  530. InsertSpaceAfterFunctionKeywordForAnonymousFunctions: true,
  531. InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
  532. InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
  533. InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
  534. PlaceOpenBraceOnNewLineForControlBlocks: false,
  535. PlaceOpenBraceOnNewLineForFunctions: false
  536. };
  537. }
  538. protected _convertTextChanges(uri: Uri, change: ts.TextChange): monaco.editor.ISingleEditOperation {
  539. return <monaco.editor.ISingleEditOperation>{
  540. text: change.newText,
  541. range: this._textSpanToRange(uri, change.span)
  542. };
  543. }
  544. }
  545. export class FormatAdapter extends FormatHelper implements monaco.languages.DocumentRangeFormattingEditProvider {
  546. provideDocumentRangeFormattingEdits(model: monaco.editor.IReadOnlyModel, range: Range, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable<monaco.editor.ISingleEditOperation[]> {
  547. const resource = model.uri;
  548. return this._worker(resource).then(worker => {
  549. return worker.getFormattingEditsForRange(resource.toString(),
  550. this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn }),
  551. this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn }),
  552. FormatHelper._convertOptions(options));
  553. }).then(edits => {
  554. if (edits) {
  555. return edits.map(edit => this._convertTextChanges(resource, edit));
  556. }
  557. });
  558. }
  559. }
  560. export class FormatOnTypeAdapter extends FormatHelper implements monaco.languages.OnTypeFormattingEditProvider {
  561. get autoFormatTriggerCharacters() {
  562. return [';', '}', '\n'];
  563. }
  564. provideOnTypeFormattingEdits(model: monaco.editor.IReadOnlyModel, position: Position, ch: string, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable<monaco.editor.ISingleEditOperation[]> {
  565. const resource = model.uri;
  566. return this._worker(resource).then(worker => {
  567. return worker.getFormattingEditsAfterKeystroke(resource.toString(),
  568. this._positionToOffset(resource, position),
  569. ch, FormatHelper._convertOptions(options));
  570. }).then(edits => {
  571. if (edits) {
  572. return edits.map(edit => this._convertTextChanges(resource, edit));
  573. }
  574. });
  575. }
  576. }
  577. // --- code actions ------
  578. export class CodeActionAdaptor extends FormatHelper implements monaco.languages.CodeActionProvider {
  579. public provideCodeActions(model: monaco.editor.ITextModel, range: Range, context: monaco.languages.CodeActionContext, token: CancellationToken): Promise<monaco.languages.CodeActionList> {
  580. const resource = model.uri;
  581. return this._worker(resource).then(worker => {
  582. const start = this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn });
  583. const end = this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn });
  584. const formatOptions = FormatHelper._convertOptions(model.getOptions());
  585. const errorCodes = context.markers.filter(m => m.code).map(m => m.code).map(Number);
  586. return worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions);
  587. }).then(codeFixes => {
  588. return codeFixes.filter(fix => {
  589. // Removes any 'make a new file'-type code fix
  590. return fix.changes.filter(change => change.isNewFile).length === 0;
  591. }).map(fix => {
  592. return this._tsCodeFixActionToMonacoCodeAction(model, context, fix);
  593. })
  594. }).then(result => {
  595. return {
  596. actions: result,
  597. dispose: () => { }
  598. };
  599. });
  600. }
  601. private _tsCodeFixActionToMonacoCodeAction(model: monaco.editor.ITextModel, context: monaco.languages.CodeActionContext, codeFix: ts.CodeFixAction): monaco.languages.CodeAction {
  602. const edits: monaco.languages.ResourceTextEdit[] = codeFix.changes.map(edit => ({
  603. resource: model.uri,
  604. edits: edit.textChanges.map(tc => ({
  605. range: this._textSpanToRange(model.uri, tc.span),
  606. text: tc.newText
  607. }))
  608. }));
  609. const action: monaco.languages.CodeAction = {
  610. title: codeFix.description,
  611. edit: { edits: edits },
  612. diagnostics: context.markers,
  613. kind: "quickfix"
  614. };
  615. return action;
  616. }
  617. }
  618. // --- rename ----
  619. export class RenameAdapter extends Adapter implements monaco.languages.RenameProvider {
  620. async provideRenameEdits(model: monaco.editor.ITextModel, position: Position, newName: string, token: CancellationToken): Promise<monaco.languages.WorkspaceEdit & monaco.languages.Rejection> {
  621. const resource = model.uri;
  622. const fileName = resource.toString();
  623. const offset = this._positionToOffset(resource, position);
  624. const worker = await this._worker(resource);
  625. const renameInfo = await worker.getRenameInfo(fileName, offset, { allowRenameOfImportPath: false });
  626. if (renameInfo.canRename === false) { // use explicit comparison so that the discriminated union gets resolved properly
  627. return {
  628. edits: [],
  629. rejectReason: renameInfo.localizedErrorMessage
  630. };
  631. }
  632. if (renameInfo.fileToRename !== undefined) {
  633. throw new Error("Renaming files is not supported.");
  634. }
  635. const renameLocations = await worker.findRenameLocations(fileName, offset, /*strings*/ false, /*comments*/ false, /*prefixAndSuffix*/ false);
  636. const fileNameToResourceTextEditMap: { [fileName: string]: monaco.languages.ResourceTextEdit } = {};
  637. const edits: monaco.languages.ResourceTextEdit[] = [];
  638. for (const renameLocation of renameLocations) {
  639. if (!(renameLocation.fileName in fileNameToResourceTextEditMap)) {
  640. const resourceTextEdit = {
  641. edits: [],
  642. resource: monaco.Uri.parse(renameLocation.fileName)
  643. };
  644. fileNameToResourceTextEditMap[renameLocation.fileName] = resourceTextEdit;
  645. edits.push(resourceTextEdit);
  646. }
  647. fileNameToResourceTextEditMap[renameLocation.fileName].edits.push({
  648. range: this._textSpanToRange(resource, renameLocation.textSpan),
  649. text: newName
  650. });
  651. }
  652. return { edits };
  653. }
  654. }