-
Notifications
You must be signed in to change notification settings - Fork 21
Multi-root workspace support and Fix TextMate grammar bugs #276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Copilot Instructions | ||
|
|
||
| ## Build, Test, and Lint | ||
|
|
||
| This is a multi-package monorepo (root, `client/`, `server/`, `scripts/`). Each has its own `node_modules` and `package.json`. The root `npm install` runs `npm install-clean` in all sub-packages via `postinstall`. | ||
|
|
||
| ```shell | ||
| npm install # install all packages (root + client + server + scripts) | ||
| npm run build # TypeScript compile all packages | ||
| npm run lint # ESLint all packages | ||
|
|
||
| # Server unit tests (mocha, no build dependency) | ||
| npm run test:server | ||
|
|
||
| # Client UI tests (vscode-test-electron, requires webpack first) | ||
| npm run webpack-prod | ||
| npm run test:client | ||
|
|
||
| # Run a single server test file directly | ||
| cd server | ||
| npx mocha --require ts-node/register src/test/<file>.test.ts | ||
|
|
||
| # Package the extension | ||
| npm run vsix | ||
| ``` | ||
|
|
||
| ## Architecture | ||
|
|
||
| This is a VS Code extension providing Language Server Protocol (LSP) support for the Power Query / M formula language. | ||
|
|
||
| **Client** (`client/src/extension.ts`): Activates the extension, starts the language server over **Node IPC**, and manages the library symbol system. Exposes a `PowerQueryApi` for other extensions. | ||
|
|
||
| **Server** (`server/src/server.ts`): Handles LSP requests — completion, hover, definition, formatting, diagnostics, rename, folding, document symbols, semantic tokens, and signature help. Request handling follows a consistent pattern: fetch document → create cancellation token → build a `PQLS.Analysis` → call the analysis API → map results to LSP types. Errors go through `ErrorUtils.handleError`. | ||
|
|
||
| **Scripts** (`scripts/`): Standalone benchmark/tooling utilities, not part of the extension runtime. | ||
|
|
||
| **Core dependencies** (Microsoft-owned, all three are used across the codebase): | ||
|
|
||
| - `@microsoft/powerquery-parser` — Lexer, parser, and type validation | ||
| - `@microsoft/powerquery-language-services` — Higher-level language service (Analysis, completions, hover, etc.) | ||
| - `@microsoft/powerquery-formatter` — Code formatter (server-side only) | ||
|
|
||
| ### Library Symbol System | ||
|
|
||
| External library symbols allow users to extend the M standard library with custom function definitions loaded from JSON files on disk. The flow: | ||
|
|
||
| 1. User configures `powerquery.client.additionalSymbolsDirectories` setting | ||
| 2. `LibrarySymbolManager` scans directories for `.json` files, parses them via `LibrarySymbolUtils` | ||
| 3. `LibrarySymbolClient` sends symbols to the server via custom LSP requests (`powerquery/addLibrarySymbols`, `powerquery/removeLibrarySymbols`) | ||
| 4. Server merges external symbols with built-in standard/SDK library in `SettingsUtils.getLibrary()` | ||
|
|
||
| ### Local Development with Sibling Packages | ||
|
|
||
| Use `npm run link:start` to develop against locally-built copies of the parser, formatter, and language-services packages (via `npm link`). Use `npm run link:stop` to revert to published npm versions. | ||
|
|
||
| ## Code Conventions | ||
|
|
||
| **TypeScript strictness** — The ESLint config enforces rules that are stricter than typical TypeScript projects: | ||
|
|
||
| - `explicit-function-return-type`: All functions must have explicit return type annotations | ||
| - `typedef`: Required on variables, parameters, properties, arrow parameters, and destructuring | ||
| - `no-floating-promises`: All promises must be awaited or handled | ||
| - `switch-exhaustiveness-check`: Switch statements must cover all cases | ||
| - `sort-imports`: Imports must be sorted (separated groups allowed, case-insensitive) | ||
| - `no-plusplus`: Use `+= 1` instead of `++` | ||
| - `object-shorthand`: Always use shorthand properties/methods | ||
| - `arrow-body-style`: Use concise arrow function bodies (no braces for single expressions) | ||
| - `curly`: Always use braces for control flow, even single-line | ||
|
|
||
| **Formatting** (Prettier): 120 char line width, 4-space indent, trailing commas, no parens on single arrow params. | ||
|
|
||
| **Import aliases**: The codebase uses `PQP` for powerquery-parser, `PQLS` for powerquery-language-services, and `PQF` for powerquery-formatter. | ||
|
|
||
| **Testing**: Server tests use Mocha (`describe`/`it`) with Chai `expect` and Node `assert`. Client tests use VS Code's test runner (`suite`/`test` TDD-style). | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -63,7 +63,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<PowerQ | |||||||||||
| librarySymbolClient = new LibrarySymbolClient(client); | ||||||||||||
| librarySymbolManager = new LibrarySymbolManager(librarySymbolClient, client); | ||||||||||||
|
|
||||||||||||
| await configureSymbolDirectories(); | ||||||||||||
| await configureAllFolderSymbolDirectories(); | ||||||||||||
|
|
||||||||||||
| context.subscriptions.push( | ||||||||||||
| vscode.workspace.onDidChangeConfiguration(async (event: vscode.ConfigurationChangeEvent) => { | ||||||||||||
|
|
@@ -73,9 +73,20 @@ export async function activate(context: vscode.ExtensionContext): Promise<PowerQ | |||||||||||
| ); | ||||||||||||
|
|
||||||||||||
| if (event.affectsConfiguration(symbolDirs)) { | ||||||||||||
| await configureSymbolDirectories(); | ||||||||||||
| await configureAllFolderSymbolDirectories(); | ||||||||||||
| } | ||||||||||||
| }), | ||||||||||||
| vscode.workspace.onDidChangeWorkspaceFolders(async (event: vscode.WorkspaceFoldersChangeEvent) => { | ||||||||||||
| await Promise.all( | ||||||||||||
| event.removed.map((folder: vscode.WorkspaceFolder) => | ||||||||||||
| librarySymbolManager.removeSymbolsForFolder(folder.uri.toString()), | ||||||||||||
| ), | ||||||||||||
| ); | ||||||||||||
|
|
||||||||||||
| await Promise.all( | ||||||||||||
| event.added.map((folder: vscode.WorkspaceFolder) => configureSymbolDirectoriesForFolder(folder)), | ||||||||||||
| ); | ||||||||||||
| }), | ||||||||||||
| ); | ||||||||||||
|
|
||||||||||||
| return Object.freeze(librarySymbolClient); | ||||||||||||
|
|
@@ -85,18 +96,37 @@ export function deactivate(): Thenable<void> | undefined { | |||||||||||
| return client?.stop(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| async function configureSymbolDirectories(): Promise<void> { | ||||||||||||
| const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(ConfigurationConstant.BasePath); | ||||||||||||
| async function configureAllFolderSymbolDirectories(): Promise<void> { | ||||||||||||
| const folders: readonly vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders; | ||||||||||||
|
|
||||||||||||
| if (!folders || folders.length === 0) { | ||||||||||||
| // No workspace folders — read global config | ||||||||||||
| const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(ConfigurationConstant.BasePath); | ||||||||||||
|
|
||||||||||||
| const additionalSymbolsDirectories: string[] | undefined = config.get( | ||||||||||||
| ConfigurationConstant.AdditionalSymbolsDirectories, | ||||||||||||
| ); | ||||||||||||
|
|
||||||||||||
| await librarySymbolManager.refreshSymbolDirectories(additionalSymbolsDirectories ?? []); | ||||||||||||
|
|
||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
||||||||||||
| // Clear any previously-registered global or stale folder symbols before | |
| // rebuilding the current workspace-folder registrations. | |
| await librarySymbolManager.removeAllSymbols(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was there a typo, or did the naming convention change (i.e. the missing leading dot)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think previously you'd configure the path(s) to instructions using settings, and then they switched it to be convention based by default. i created the original file - this one was created with the agency /init switch.