extension.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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. import * as path from "path";
  6. import {
  7. commands,
  8. CompletionList,
  9. ExtensionContext,
  10. Uri,
  11. workspace,
  12. } from "vscode";
  13. import { getLanguageService } from "vscode-html-languageservice";
  14. import {
  15. LanguageClient,
  16. LanguageClientOptions,
  17. ServerOptions,
  18. TransportKind,
  19. } from "vscode-languageclient";
  20. import { isInsideHtmlMacro } from "./rustSupport";
  21. // import { getCSSVirtualContent, isInsideStyleRegion } from "./embeddedSupport";
  22. let client: LanguageClient;
  23. const htmlLanguageService = getLanguageService();
  24. export function activate(context: ExtensionContext) {
  25. // The server is implemented in node
  26. let serverModule = context.asAbsolutePath(
  27. path.join("server", "out", "server.js")
  28. );
  29. // The debug options for the server
  30. // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
  31. let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
  32. // If the extension is launched in debug mode then the debug server options are used
  33. // Otherwise the run options are used
  34. let serverOptions: ServerOptions = {
  35. run: { module: serverModule, transport: TransportKind.ipc },
  36. debug: {
  37. module: serverModule,
  38. transport: TransportKind.ipc,
  39. options: debugOptions,
  40. },
  41. };
  42. const virtualDocumentContents = new Map<string, string>();
  43. workspace.registerTextDocumentContentProvider("embedded-content", {
  44. provideTextDocumentContent: (uri) => {
  45. const originalUri = uri.path.slice(1).slice(0, -4);
  46. console.error(originalUri);
  47. const decodedUri = decodeURIComponent(originalUri);
  48. return virtualDocumentContents.get(decodedUri);
  49. },
  50. });
  51. let clientOptions: LanguageClientOptions = {
  52. documentSelector: [{ scheme: "file", language: "rust" }],
  53. middleware: {
  54. provideCompletionItem: async (
  55. document,
  56. position,
  57. context,
  58. token,
  59. next
  60. ) => {
  61. /*
  62. 1: Find the occurences of the html! macro using regex
  63. 2: Check if any of the occurences match the cursor offset
  64. 3: If so, direct the captured block to the html to the rsx language service
  65. */
  66. const docSrc = document.getText();
  67. const offset = document.offsetAt(position);
  68. const matches = docSrc.matchAll(macroRegex);
  69. // Lazily loop through matches, abort early if the cursor is after the match
  70. // let start = 0;
  71. // let end = 0;
  72. let matchBody: string | undefined = undefined;
  73. for (const match of matches) {
  74. // // Check if the cursor is inbetween the previous end and the new start
  75. // // This means the cursor is between html! invocations and we should bail early
  76. // if (offset > end && offset < match.index) {
  77. // // Ensure the match
  78. // // defer to the "next?" symbol
  79. // return await next(document, position, context, token);
  80. // }
  81. // Otherwise, move the counters forward
  82. const start = match.index;
  83. const end = start + match.length;
  84. // Ensure the cursor is within the match
  85. // Break if so
  86. if (offset >= start && offset <= end) {
  87. matchBody = match[1];
  88. break;
  89. }
  90. }
  91. // If we looped through all the matches and the match wasn't defined, then bail
  92. if (matchBody === undefined) {
  93. return await next(document, position, context, token);
  94. }
  95. // If we're inside the style region, then provide CSS completions with the CSS provider
  96. const originalUri = document.uri.toString();
  97. virtualDocumentContents.set(originalUri, matchBody);
  98. // getCSSVirtualContent(htmlLanguageService, document.getText())
  99. const vdocUriString = `embedded-content://html/${encodeURIComponent(
  100. originalUri
  101. )}.html`;
  102. const vdocUri = Uri.parse(vdocUriString);
  103. return await commands.executeCommand<CompletionList>(
  104. "vscode.executeCompletionItemProvider",
  105. vdocUri,
  106. position,
  107. context.triggerCharacter
  108. );
  109. },
  110. },
  111. };
  112. // Create the language client and start the client.
  113. client = new LanguageClient(
  114. "languageServerExample",
  115. "Language Server Example",
  116. serverOptions,
  117. clientOptions
  118. );
  119. // Start the client. This will also launch the server
  120. client.start();
  121. }
  122. export function deactivate(): Thenable<void> | undefined {
  123. if (!client) {
  124. return undefined;
  125. }
  126. return client.stop();
  127. }
  128. const macroRegex = /html! {([\s\S]*?)}/g;