languageFeatures.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  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. .map(d => this._convertDiagnostics(resource, d));
  157. monaco.editor.setModelMarkers(monaco.editor.getModel(resource), this._selector, markers);
  158. }).then(undefined, err => {
  159. console.error(err);
  160. });
  161. }
  162. private _convertDiagnostics(resource: Uri, diag: ts.Diagnostic): monaco.editor.IMarkerData {
  163. const { lineNumber: startLineNumber, column: startColumn } = this._offsetToPosition(resource, diag.start);
  164. const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(resource, diag.start + diag.length);
  165. return {
  166. severity: this._tsDiagnosticCategoryToMarkerSeverity(diag.category),
  167. startLineNumber,
  168. startColumn,
  169. endLineNumber,
  170. endColumn,
  171. message: flattenDiagnosticMessageText(diag.messageText, '\n'),
  172. code: diag.code.toString(),
  173. relatedInformation: this._convertRelatedInformation(resource, diag.relatedInformation)
  174. };
  175. }
  176. private _convertRelatedInformation(resource: Uri, relatedInformation?: ts.DiagnosticRelatedInformation[]): monaco.editor.IRelatedInformation[] {
  177. if (relatedInformation === undefined)
  178. return undefined;
  179. return relatedInformation.map(info => {
  180. const relatedResource = info.file === undefined ? resource : monaco.Uri.parse(info.file.fileName);
  181. const { lineNumber: startLineNumber, column: startColumn } = this._offsetToPosition(relatedResource, info.start);
  182. const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(relatedResource, info.start + info.length);
  183. return {
  184. resource: relatedResource,
  185. startLineNumber,
  186. startColumn,
  187. endLineNumber,
  188. endColumn,
  189. message: flattenDiagnosticMessageText(info.messageText, '\n')
  190. };
  191. });
  192. }
  193. private _tsDiagnosticCategoryToMarkerSeverity(category: ts.DiagnosticCategory): monaco.MarkerSeverity {
  194. switch (category) {
  195. case ts.DiagnosticCategory.Error: return monaco.MarkerSeverity.Error
  196. case ts.DiagnosticCategory.Message: return monaco.MarkerSeverity.Info
  197. case ts.DiagnosticCategory.Warning: return monaco.MarkerSeverity.Warning
  198. case ts.DiagnosticCategory.Suggestion: return monaco.MarkerSeverity.Hint
  199. }
  200. }
  201. }
  202. // --- suggest ------
  203. interface MyCompletionItem extends monaco.languages.CompletionItem {
  204. uri: Uri;
  205. position: Position;
  206. }
  207. export class SuggestAdapter extends Adapter implements monaco.languages.CompletionItemProvider {
  208. public get triggerCharacters(): string[] {
  209. return ['.'];
  210. }
  211. provideCompletionItems(model: monaco.editor.IReadOnlyModel, position: Position, _context: monaco.languages.CompletionContext, token: CancellationToken): Thenable<monaco.languages.CompletionList> {
  212. const wordInfo = model.getWordUntilPosition(position);
  213. const wordRange = new Range(position.lineNumber, wordInfo.startColumn, position.lineNumber, wordInfo.endColumn);
  214. const resource = model.uri;
  215. const offset = this._positionToOffset(resource, position);
  216. return this._worker(resource).then(worker => {
  217. return worker.getCompletionsAtPosition(resource.toString(), offset);
  218. }).then(info => {
  219. if (!info) {
  220. return;
  221. }
  222. let suggestions: MyCompletionItem[] = info.entries.map(entry => {
  223. let range = wordRange;
  224. if (entry.replacementSpan) {
  225. const p1 = model.getPositionAt(entry.replacementSpan.start);
  226. const p2 = model.getPositionAt(entry.replacementSpan.start + entry.replacementSpan.length);
  227. range = new Range(p1.lineNumber, p1.column, p2.lineNumber, p2.column);
  228. }
  229. return {
  230. uri: resource,
  231. position: position,
  232. range: range,
  233. label: entry.name,
  234. insertText: entry.name,
  235. sortText: entry.sortText,
  236. kind: SuggestAdapter.convertKind(entry.kind)
  237. };
  238. });
  239. return {
  240. suggestions
  241. };
  242. });
  243. }
  244. resolveCompletionItem(_model: monaco.editor.IReadOnlyModel, _position: Position, item: monaco.languages.CompletionItem, token: CancellationToken): Thenable<monaco.languages.CompletionItem> {
  245. let myItem = <MyCompletionItem>item;
  246. const resource = myItem.uri;
  247. const position = myItem.position;
  248. return this._worker(resource).then(worker => {
  249. return worker.getCompletionEntryDetails(resource.toString(),
  250. this._positionToOffset(resource, position),
  251. myItem.label);
  252. }).then(details => {
  253. if (!details) {
  254. return myItem;
  255. }
  256. return <MyCompletionItem>{
  257. uri: resource,
  258. position: position,
  259. label: details.name,
  260. kind: SuggestAdapter.convertKind(details.kind),
  261. detail: displayPartsToString(details.displayParts),
  262. documentation: {
  263. value: displayPartsToString(details.documentation)
  264. }
  265. };
  266. });
  267. }
  268. private static convertKind(kind: string): monaco.languages.CompletionItemKind {
  269. switch (kind) {
  270. case Kind.primitiveType:
  271. case Kind.keyword:
  272. return monaco.languages.CompletionItemKind.Keyword;
  273. case Kind.variable:
  274. case Kind.localVariable:
  275. return monaco.languages.CompletionItemKind.Variable;
  276. case Kind.memberVariable:
  277. case Kind.memberGetAccessor:
  278. case Kind.memberSetAccessor:
  279. return monaco.languages.CompletionItemKind.Field;
  280. case Kind.function:
  281. case Kind.memberFunction:
  282. case Kind.constructSignature:
  283. case Kind.callSignature:
  284. case Kind.indexSignature:
  285. return monaco.languages.CompletionItemKind.Function;
  286. case Kind.enum:
  287. return monaco.languages.CompletionItemKind.Enum;
  288. case Kind.module:
  289. return monaco.languages.CompletionItemKind.Module;
  290. case Kind.class:
  291. return monaco.languages.CompletionItemKind.Class;
  292. case Kind.interface:
  293. return monaco.languages.CompletionItemKind.Interface;
  294. case Kind.warning:
  295. return monaco.languages.CompletionItemKind.File;
  296. }
  297. return monaco.languages.CompletionItemKind.Property;
  298. }
  299. }
  300. export class SignatureHelpAdapter extends Adapter implements monaco.languages.SignatureHelpProvider {
  301. public signatureHelpTriggerCharacters = ['(', ','];
  302. provideSignatureHelp(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.SignatureHelpResult> {
  303. let resource = model.uri;
  304. return this._worker(resource).then(worker => worker.getSignatureHelpItems(resource.toString(), this._positionToOffset(resource, position))).then(info => {
  305. if (!info) {
  306. return;
  307. }
  308. let ret: monaco.languages.SignatureHelp = {
  309. activeSignature: info.selectedItemIndex,
  310. activeParameter: info.argumentIndex,
  311. signatures: []
  312. };
  313. info.items.forEach(item => {
  314. let signature: monaco.languages.SignatureInformation = {
  315. label: '',
  316. parameters: []
  317. };
  318. signature.label += displayPartsToString(item.prefixDisplayParts);
  319. item.parameters.forEach((p, i, a) => {
  320. let label = displayPartsToString(p.displayParts);
  321. let parameter: monaco.languages.ParameterInformation = {
  322. label: label,
  323. documentation: displayPartsToString(p.documentation)
  324. };
  325. signature.label += label;
  326. signature.parameters.push(parameter);
  327. if (i < a.length - 1) {
  328. signature.label += displayPartsToString(item.separatorDisplayParts);
  329. }
  330. });
  331. signature.label += displayPartsToString(item.suffixDisplayParts);
  332. ret.signatures.push(signature);
  333. });
  334. return {
  335. value: ret,
  336. dispose() { }
  337. };
  338. });
  339. }
  340. }
  341. // --- hover ------
  342. export class QuickInfoAdapter extends Adapter implements monaco.languages.HoverProvider {
  343. provideHover(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.Hover> {
  344. let resource = model.uri;
  345. return this._worker(resource).then(worker => {
  346. return worker.getQuickInfoAtPosition(resource.toString(), this._positionToOffset(resource, position));
  347. }).then(info => {
  348. if (!info) {
  349. return;
  350. }
  351. let documentation = displayPartsToString(info.documentation);
  352. let tags = info.tags ? info.tags.map(tag => {
  353. const label = `*@${tag.name}*`;
  354. if (!tag.text) {
  355. return label;
  356. }
  357. return label + (tag.text.match(/\r\n|\n/g) ? ' \n' + tag.text : ` - ${tag.text}`);
  358. })
  359. .join(' \n\n') : '';
  360. let contents = displayPartsToString(info.displayParts);
  361. return {
  362. range: this._textSpanToRange(resource, info.textSpan),
  363. contents: [{
  364. value: '```js\n' + contents + '\n```\n'
  365. }, {
  366. value: documentation + (tags ? '\n\n' + tags : '')
  367. }]
  368. };
  369. });
  370. }
  371. }
  372. // --- occurrences ------
  373. export class OccurrencesAdapter extends Adapter implements monaco.languages.DocumentHighlightProvider {
  374. public provideDocumentHighlights(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.DocumentHighlight[]> {
  375. const resource = model.uri;
  376. return this._worker(resource).then(worker => {
  377. return worker.getOccurrencesAtPosition(resource.toString(), this._positionToOffset(resource, position));
  378. }).then(entries => {
  379. if (!entries) {
  380. return;
  381. }
  382. return entries.map(entry => {
  383. return <monaco.languages.DocumentHighlight>{
  384. range: this._textSpanToRange(resource, entry.textSpan),
  385. kind: entry.isWriteAccess ? monaco.languages.DocumentHighlightKind.Write : monaco.languages.DocumentHighlightKind.Text
  386. };
  387. });
  388. });
  389. }
  390. }
  391. // --- definition ------
  392. export class DefinitionAdapter extends Adapter {
  393. public provideDefinition(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.Definition> {
  394. const resource = model.uri;
  395. return this._worker(resource).then(worker => {
  396. return worker.getDefinitionAtPosition(resource.toString(), this._positionToOffset(resource, position));
  397. }).then(entries => {
  398. if (!entries) {
  399. return;
  400. }
  401. const result: monaco.languages.Location[] = [];
  402. for (let entry of entries) {
  403. const uri = Uri.parse(entry.fileName);
  404. if (monaco.editor.getModel(uri)) {
  405. result.push({
  406. uri: uri,
  407. range: this._textSpanToRange(uri, entry.textSpan)
  408. });
  409. }
  410. }
  411. return result;
  412. });
  413. }
  414. }
  415. // --- references ------
  416. export class ReferenceAdapter extends Adapter implements monaco.languages.ReferenceProvider {
  417. provideReferences(model: monaco.editor.IReadOnlyModel, position: Position, context: monaco.languages.ReferenceContext, token: CancellationToken): Thenable<monaco.languages.Location[]> {
  418. const resource = model.uri;
  419. return this._worker(resource).then(worker => {
  420. return worker.getReferencesAtPosition(resource.toString(), this._positionToOffset(resource, position));
  421. }).then(entries => {
  422. if (!entries) {
  423. return;
  424. }
  425. const result: monaco.languages.Location[] = [];
  426. for (let entry of entries) {
  427. const uri = Uri.parse(entry.fileName);
  428. if (monaco.editor.getModel(uri)) {
  429. result.push({
  430. uri: uri,
  431. range: this._textSpanToRange(uri, entry.textSpan)
  432. });
  433. }
  434. }
  435. return result;
  436. });
  437. }
  438. }
  439. // --- outline ------
  440. export class OutlineAdapter extends Adapter implements monaco.languages.DocumentSymbolProvider {
  441. public provideDocumentSymbols(model: monaco.editor.IReadOnlyModel, token: CancellationToken): Thenable<monaco.languages.DocumentSymbol[]> {
  442. const resource = model.uri;
  443. return this._worker(resource).then(worker => worker.getNavigationBarItems(resource.toString())).then(items => {
  444. if (!items) {
  445. return;
  446. }
  447. const convert = (bucket: monaco.languages.DocumentSymbol[], item: ts.NavigationBarItem, containerLabel?: string): void => {
  448. let result: monaco.languages.DocumentSymbol = {
  449. name: item.text,
  450. detail: '',
  451. kind: <monaco.languages.SymbolKind>(outlineTypeTable[item.kind] || monaco.languages.SymbolKind.Variable),
  452. range: this._textSpanToRange(resource, item.spans[0]),
  453. selectionRange: this._textSpanToRange(resource, item.spans[0]),
  454. tags: [],
  455. containerName: containerLabel
  456. };
  457. if (item.childItems && item.childItems.length > 0) {
  458. for (let child of item.childItems) {
  459. convert(bucket, child, result.name);
  460. }
  461. }
  462. bucket.push(result);
  463. }
  464. let result: monaco.languages.DocumentSymbol[] = [];
  465. items.forEach(item => convert(result, item));
  466. return result;
  467. });
  468. }
  469. }
  470. export class Kind {
  471. public static unknown: string = '';
  472. public static keyword: string = 'keyword';
  473. public static script: string = 'script';
  474. public static module: string = 'module';
  475. public static class: string = 'class';
  476. public static interface: string = 'interface';
  477. public static type: string = 'type';
  478. public static enum: string = 'enum';
  479. public static variable: string = 'var';
  480. public static localVariable: string = 'local var';
  481. public static function: string = 'function';
  482. public static localFunction: string = 'local function';
  483. public static memberFunction: string = 'method';
  484. public static memberGetAccessor: string = 'getter';
  485. public static memberSetAccessor: string = 'setter';
  486. public static memberVariable: string = 'property';
  487. public static constructorImplementation: string = 'constructor';
  488. public static callSignature: string = 'call';
  489. public static indexSignature: string = 'index';
  490. public static constructSignature: string = 'construct';
  491. public static parameter: string = 'parameter';
  492. public static typeParameter: string = 'type parameter';
  493. public static primitiveType: string = 'primitive type';
  494. public static label: string = 'label';
  495. public static alias: string = 'alias';
  496. public static const: string = 'const';
  497. public static let: string = 'let';
  498. public static warning: string = 'warning';
  499. }
  500. let outlineTypeTable: { [kind: string]: monaco.languages.SymbolKind } = Object.create(null);
  501. outlineTypeTable[Kind.module] = monaco.languages.SymbolKind.Module;
  502. outlineTypeTable[Kind.class] = monaco.languages.SymbolKind.Class;
  503. outlineTypeTable[Kind.enum] = monaco.languages.SymbolKind.Enum;
  504. outlineTypeTable[Kind.interface] = monaco.languages.SymbolKind.Interface;
  505. outlineTypeTable[Kind.memberFunction] = monaco.languages.SymbolKind.Method;
  506. outlineTypeTable[Kind.memberVariable] = monaco.languages.SymbolKind.Property;
  507. outlineTypeTable[Kind.memberGetAccessor] = monaco.languages.SymbolKind.Property;
  508. outlineTypeTable[Kind.memberSetAccessor] = monaco.languages.SymbolKind.Property;
  509. outlineTypeTable[Kind.variable] = monaco.languages.SymbolKind.Variable;
  510. outlineTypeTable[Kind.const] = monaco.languages.SymbolKind.Variable;
  511. outlineTypeTable[Kind.localVariable] = monaco.languages.SymbolKind.Variable;
  512. outlineTypeTable[Kind.variable] = monaco.languages.SymbolKind.Variable;
  513. outlineTypeTable[Kind.function] = monaco.languages.SymbolKind.Function;
  514. outlineTypeTable[Kind.localFunction] = monaco.languages.SymbolKind.Function;
  515. // --- formatting ----
  516. export abstract class FormatHelper extends Adapter {
  517. protected static _convertOptions(options: monaco.languages.FormattingOptions): ts.FormatCodeOptions {
  518. return {
  519. ConvertTabsToSpaces: options.insertSpaces,
  520. TabSize: options.tabSize,
  521. IndentSize: options.tabSize,
  522. IndentStyle: IndentStyle.Smart,
  523. NewLineCharacter: '\n',
  524. InsertSpaceAfterCommaDelimiter: true,
  525. InsertSpaceAfterSemicolonInForStatements: true,
  526. InsertSpaceBeforeAndAfterBinaryOperators: true,
  527. InsertSpaceAfterKeywordsInControlFlowStatements: true,
  528. InsertSpaceAfterFunctionKeywordForAnonymousFunctions: true,
  529. InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
  530. InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
  531. InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
  532. PlaceOpenBraceOnNewLineForControlBlocks: false,
  533. PlaceOpenBraceOnNewLineForFunctions: false
  534. };
  535. }
  536. protected _convertTextChanges(uri: Uri, change: ts.TextChange): monaco.editor.ISingleEditOperation {
  537. return <monaco.editor.ISingleEditOperation>{
  538. text: change.newText,
  539. range: this._textSpanToRange(uri, change.span)
  540. };
  541. }
  542. }
  543. export class FormatAdapter extends FormatHelper implements monaco.languages.DocumentRangeFormattingEditProvider {
  544. provideDocumentRangeFormattingEdits(model: monaco.editor.IReadOnlyModel, range: Range, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable<monaco.editor.ISingleEditOperation[]> {
  545. const resource = model.uri;
  546. return this._worker(resource).then(worker => {
  547. return worker.getFormattingEditsForRange(resource.toString(),
  548. this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn }),
  549. this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn }),
  550. FormatHelper._convertOptions(options));
  551. }).then(edits => {
  552. if (edits) {
  553. return edits.map(edit => this._convertTextChanges(resource, edit));
  554. }
  555. });
  556. }
  557. }
  558. export class FormatOnTypeAdapter extends FormatHelper implements monaco.languages.OnTypeFormattingEditProvider {
  559. get autoFormatTriggerCharacters() {
  560. return [';', '}', '\n'];
  561. }
  562. provideOnTypeFormattingEdits(model: monaco.editor.IReadOnlyModel, position: Position, ch: string, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable<monaco.editor.ISingleEditOperation[]> {
  563. const resource = model.uri;
  564. return this._worker(resource).then(worker => {
  565. return worker.getFormattingEditsAfterKeystroke(resource.toString(),
  566. this._positionToOffset(resource, position),
  567. ch, FormatHelper._convertOptions(options));
  568. }).then(edits => {
  569. if (edits) {
  570. return edits.map(edit => this._convertTextChanges(resource, edit));
  571. }
  572. });
  573. }
  574. }
  575. // --- code actions ------
  576. export class CodeActionAdaptor extends FormatHelper implements monaco.languages.CodeActionProvider {
  577. public provideCodeActions(model: monaco.editor.ITextModel, range: Range, context: monaco.languages.CodeActionContext, token: CancellationToken): Promise<monaco.languages.CodeActionList> {
  578. const resource = model.uri;
  579. return this._worker(resource).then(worker => {
  580. const start = this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn });
  581. const end = this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn });
  582. const formatOptions = FormatHelper._convertOptions(model.getOptions());
  583. const errorCodes = context.markers.filter(m => m.code).map(m => m.code).map(Number);
  584. return worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions);
  585. }).then(codeFixes => {
  586. return codeFixes.filter(fix => {
  587. // Removes any 'make a new file'-type code fix
  588. return fix.changes.filter(change => change.isNewFile).length === 0;
  589. }).map(fix => {
  590. return this._tsCodeFixActionToMonacoCodeAction(model, context, fix);
  591. })
  592. }).then(result => {
  593. return {
  594. actions: result,
  595. dispose: () => { }
  596. };
  597. });
  598. }
  599. private _tsCodeFixActionToMonacoCodeAction(model: monaco.editor.ITextModel, context: monaco.languages.CodeActionContext, codeFix: ts.CodeFixAction): monaco.languages.CodeAction {
  600. const edits: monaco.languages.ResourceTextEdit[] = codeFix.changes.map(edit => ({
  601. resource: model.uri,
  602. edits: edit.textChanges.map(tc => ({
  603. range: this._textSpanToRange(model.uri, tc.span),
  604. text: tc.newText
  605. }))
  606. }));
  607. const action: monaco.languages.CodeAction = {
  608. title: codeFix.description,
  609. edit: { edits: edits },
  610. diagnostics: context.markers,
  611. kind: "quickfix"
  612. };
  613. return action;
  614. }
  615. }
  616. // --- rename ----
  617. export class RenameAdapter extends Adapter implements monaco.languages.RenameProvider {
  618. async provideRenameEdits(model: monaco.editor.ITextModel, position: Position, newName: string, token: CancellationToken): Promise<monaco.languages.WorkspaceEdit & monaco.languages.Rejection> {
  619. const resource = model.uri;
  620. const fileName = resource.toString();
  621. const offset = this._positionToOffset(resource, position);
  622. const worker = await this._worker(resource);
  623. const renameInfo = await worker.getRenameInfo(fileName, offset, { allowRenameOfImportPath: false });
  624. if (renameInfo.canRename === false) { // use explicit comparison so that the discriminated union gets resolved properly
  625. return {
  626. edits: [],
  627. rejectReason: renameInfo.localizedErrorMessage
  628. };
  629. }
  630. if (renameInfo.fileToRename !== undefined) {
  631. throw new Error("Renaming files is not supported.");
  632. }
  633. const renameLocations = await worker.findRenameLocations(fileName, offset, /*strings*/ false, /*comments*/ false, /*prefixAndSuffix*/ false);
  634. const fileNameToResourceTextEditMap: { [fileName: string]: monaco.languages.ResourceTextEdit } = {};
  635. const edits: monaco.languages.ResourceTextEdit[] = [];
  636. for (const renameLocation of renameLocations) {
  637. if (!(renameLocation.fileName in fileNameToResourceTextEditMap)) {
  638. const resourceTextEdit = {
  639. edits: [],
  640. resource: monaco.Uri.parse(renameLocation.fileName)
  641. };
  642. fileNameToResourceTextEditMap[renameLocation.fileName] = resourceTextEdit;
  643. edits.push(resourceTextEdit);
  644. }
  645. fileNameToResourceTextEditMap[renameLocation.fileName].edits.push({
  646. range: this._textSpanToRange(resource, renameLocation.textSpan),
  647. text: newName
  648. });
  649. }
  650. return { edits };
  651. }
  652. }