Auto merge of #12294 - listochkin:prettier, r=Veykril

Switch to Prettier for TypeScript Code formatting

## Summary of changes:

 1. Added [`.editorconfig` file](https://editorconfig.org) to dictate general hygienic stuff like character encoding, no trailing whitespace, new line symbols etc. for all files (e.g. Markdown). Install an editor plugin to get this rudimentary formatting assistance automatically. Prettier can read this file and, for example, use it for indentation style and size.
 2. Added a minimal prettier config file. All options are default except line width, which per [Veykril](https://github.com/Veykril) suggestion is set to 100 instead of 80, because [that's what `Rustfmt` uses](https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#max_width).
 3. Change `package.json` to use Prettier instead of `tsfmt` for code formatting.
 4. Performed initial formatting in a separate commit, per [bjorn3](https://github.com/bjorn3) suggestion added its hash to a `.git-blame-ignore-revs` file. For it to work you need to add a configuration to your git installation:
    ```Shell
    git config --global blame.ignoreRevsFile .git-blame-ignore-revs
    ```
 5. Finally, removed `typescript-formatter` from the list of dependencies.

----
What follows below is summary of the discussion we had on Zulip about the formatter switch:

## Background

For the context, there are three reasons why we went with `tsfmt` originally:
* stick to vscode default/built-in
* don't add extra deps to package.json.lock
* follow upstream (language server node I think still uses `tsfmt`)

And the meta reason here was that we didn't have anyone familiar with frontend, so went for the simplest option, at the expense of features and convenience.

Meanwhile, [**Prettier**](https://prettier.io) became a formatter project that JS community consolidated on a few years ago. It's similar to `go fmt` / `cargo fmt` in spirit: minimal to no configuration to promote general uniformity in the ecosystem. There are some options, that were needed early on to make sure the project gained momentum, but by no means it's a customizable formatter that is easy to adjust to reduce the number of changes for adoption.

## Overview of changes performed by Prettier

Some of the changes are acceptable. Prettier dictates a unified string quoting style, and as a result half of our imports at the top are changed. No one would mind that. Some one-line changes are due to string quotes, too, and although these a re numerous, the surrounding lines aren't changed, and git blame / GitLens will still show relevant context.

Some are toss ups. `trailingComma` option - set it to `none`, and get a bunch of meaningless changes in half of the code. set it to `all` and get a bunch of changes in the other half of the code. Same with using parentheses around single parameters in arrow functions: `x => x + 1` vs `(x) => x + 1`. Perrier forces one style or the other, but we use both in our code.

Like I said, the changes above are Ok - they take a single line, don't disrupt GitLens / git blame much. **The big one is line width**. Prettier wants you to choose one and stick to it. The default is 80 and it forces some reformatting to squish deeply nested code or long function type declarations. If I set it to 100-120, then Prettier finds other parts of code where a multi-line expression can be smashed into a single long line. The problem is that in both cases some of the lines that get changed are interesting, they contain somewhat non-trivial logic, and if I were to work on them in future I would love to see the commit annotations that tell me something relevant. Alas, we use some of that.

## Project impact

Though Prettier is a mainstream JS project it has no dependencies. We add another package so that it and ESLint work together nicely, and that's it.
This commit is contained in:
bors 2022-05-17 18:50:00 +00:00
commit 1a5925dc84
30 changed files with 1255 additions and 966 deletions

19
.editorconfig Normal file
View file

@ -0,0 +1,19 @@
# https://EditorConfig.org
root = true
[*]
charset = utf-8
trim_trailing_whitespace = true
end_of_line = lf
insert_final_newline = true
indent_style = space
[*.{rs,toml}]
indent_size = 4
[*.ts]
indent_size = 4
[*.js]
indent_size = 4
[*.json]
indent_size = 4

8
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,8 @@
# for this file to take effect make sure you use git ^2.23 and
# add ignoreFile to your git configuration:
# ```
# git config --global blame.ignoreRevsFile .git-blame-ignore-revs
# ```
# prettier format
f247090558c9ba3c551566eae5882b7ca865225f

View file

@ -1,40 +1,37 @@
module.exports = {
"env": {
"es6": true,
"node": true
env: {
es6: true,
node: true,
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.eslint.json",
"tsconfigRootDir": __dirname,
"sourceType": "module"
extends: ["prettier"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "tsconfig.eslint.json",
tsconfigRootDir: __dirname,
sourceType: "module",
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"camelcase": ["error"],
"eqeqeq": ["error", "always", { "null": "ignore" }],
plugins: ["@typescript-eslint"],
rules: {
camelcase: ["error"],
eqeqeq: ["error", "always", { null: "ignore" }],
curly: ["error", "multi-line"],
"no-console": ["error", { allow: ["warn", "error"] }],
"prefer-const": "error",
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
multiline: {
delimiter: "semi",
requireLast: true,
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/semi": [
"error",
"always"
singleline: {
delimiter: "semi",
requireLast: false,
},
},
],
"@typescript-eslint/semi": ["error", "always"],
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-floating-promises": "error"
}
"@typescript-eslint/no-floating-promises": "error",
},
};

View file

@ -0,0 +1,3 @@
node_modules
.vscode-test
out

View file

@ -0,0 +1,5 @@
module.exports = {
// use 100 because it's Rustfmt's default
// https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#max_width
printWidth: 100,
};

View file

@ -1,7 +1,7 @@
{
"comments": {
"lineComment": "//",
"blockComment": [ "/*", "*/" ]
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
@ -9,10 +9,10 @@
["(", ")"]
],
"colorizedBracketPairs": [
["{", "}"],
["[", "]"],
["(", ")"]
],
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },

View file

@ -1,12 +1,12 @@
{
"name": "rust-analyzer",
"version": "0.4.0-dev",
"version": "0.5.0-dev",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "rust-analyzer",
"version": "0.4.0-dev",
"version": "0.5.0-dev",
"license": "MIT OR Apache-2.0",
"dependencies": {
"d3": "^7.3.0",
@ -22,9 +22,10 @@
"cross-env": "^7.0.3",
"esbuild": "^0.14.27",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.6.2",
"tslib": "^2.3.0",
"typescript": "^4.6.3",
"typescript-formatter": "^7.2.2",
"vsce": "^2.7.0"
},
"engines": {
@ -768,12 +769,6 @@
"node": ">= 10"
}
},
"node_modules/commandpost": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.4.0.tgz",
"integrity": "sha512-aE2Y4MTFJ870NuB/+2z1cXBhSBBzRydVVjzhFC4gtenEhpnj15yu0qptWGJsO9YGrcPZ3ezX8AWb1VA391MKpQ==",
"dev": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1492,52 +1487,6 @@
"readable-stream": "^2.0.2"
}
},
"node_modules/editorconfig": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
"integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==",
"dev": true,
"dependencies": {
"commander": "^2.19.0",
"lru-cache": "^4.1.5",
"semver": "^5.6.0",
"sigmund": "^1.0.1"
},
"bin": {
"editorconfig": "bin/editorconfig"
}
},
"node_modules/editorconfig/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/editorconfig/node_modules/lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"dev": true,
"dependencies": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"node_modules/editorconfig/node_modules/semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/editorconfig/node_modules/yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -1988,6 +1937,18 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -3130,18 +3091,27 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
"integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"node_modules/pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@ -3412,12 +3382,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sigmund": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
"dev": true
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@ -3734,25 +3698,6 @@
"node": ">=4.2.0"
}
},
"node_modules/typescript-formatter": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-7.2.2.tgz",
"integrity": "sha512-V7vfI9XArVhriOTYHPzMU2WUnm5IMdu9X/CPxs8mIMGxmTBFpDABlbkBka64PZJ9/xgQeRpK8KzzAG4MPzxBDQ==",
"dev": true,
"dependencies": {
"commandpost": "^1.0.0",
"editorconfig": "^0.15.0"
},
"bin": {
"tsfmt": "bin/tsfmt"
},
"engines": {
"node": ">= 4.2.0"
},
"peerDependencies": {
"typescript": "^2.1.6 || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev"
}
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
@ -4659,12 +4604,6 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
},
"commandpost": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.4.0.tgz",
"integrity": "sha512-aE2Y4MTFJ870NuB/+2z1cXBhSBBzRydVVjzhFC4gtenEhpnj15yu0qptWGJsO9YGrcPZ3ezX8AWb1VA391MKpQ==",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -5207,48 +5146,6 @@
"readable-stream": "^2.0.2"
}
},
"editorconfig": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
"integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==",
"dev": true,
"requires": {
"commander": "^2.19.0",
"lru-cache": "^4.1.5",
"semver": "^5.6.0",
"sigmund": "^1.0.1"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"dev": true,
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
}
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -5509,6 +5406,13 @@
}
}
},
"eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"requires": {}
},
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -6386,18 +6290,18 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
"prettier": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
"integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
"dev": true
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@ -6587,12 +6491,6 @@
"object-inspect": "^1.9.0"
}
},
"sigmund": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
"dev": true
},
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@ -6820,16 +6718,6 @@
"integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
"dev": true
},
"typescript-formatter": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-7.2.2.tgz",
"integrity": "sha512-V7vfI9XArVhriOTYHPzMU2WUnm5IMdu9X/CPxs8mIMGxmTBFpDABlbkBka64PZJ9/xgQeRpK8KzzAG4MPzxBDQ==",
"dev": true,
"requires": {
"commandpost": "^1.0.0",
"editorconfig": "^0.15.0"
}
},
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",

View file

@ -29,8 +29,8 @@
"build-base": "esbuild ./src/main.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node --target=node16",
"build": "npm run build-base -- --sourcemap",
"watch": "npm run build-base -- --sourcemap --watch",
"lint": "tsfmt --verify && eslint -c .eslintrc.js --ext ts ./src ./tests",
"fix": " tsfmt -r && eslint -c .eslintrc.js --ext ts ./src ./tests --fix",
"lint": "prettier --check . && eslint -c .eslintrc.js --ext ts ./src ./tests",
"fix": "prettier --write . && eslint -c .eslintrc.js --ext ts ./src ./tests --fix",
"pretest": "tsc && npm run build",
"test": "cross-env TEST_VARIABLE=test node ./out/tests/runTests.js"
},
@ -48,9 +48,10 @@
"cross-env": "^7.0.3",
"esbuild": "^0.14.27",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.6.2",
"tslib": "^2.3.0",
"typescript": "^4.6.3",
"typescript-formatter": "^7.2.2",
"vsce": "^2.7.0"
},
"activationEvents": [

View file

@ -25,7 +25,5 @@
"name": "string"
}
},
"fileTypes": [
"rast"
]
"fileTypes": ["rast"]
}

View file

@ -1,13 +1,13 @@
import * as vscode from 'vscode';
import * as vscode from "vscode";
import { Ctx, Disposable } from './ctx';
import { RustEditor, isRustEditor } from './util';
import { Ctx, Disposable } from "./ctx";
import { RustEditor, isRustEditor } from "./util";
// FIXME: consider implementing this via the Tree View API?
// https://code.visualstudio.com/api/extension-guides/tree-view
export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable {
private readonly astDecorationType = vscode.window.createTextEditorDecorationType({
borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'),
borderColor: new vscode.ThemeColor("rust_analyzer.syntaxTreeBorder"),
borderStyle: "solid",
borderWidth: "2px",
});
@ -35,11 +35,23 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
});
constructor(ctx: Ctx) {
ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: 'rust-analyzer' }, this));
ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this));
ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this));
vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions);
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions);
vscode.workspace.onDidCloseTextDocument(
this.onDidCloseTextDocument,
this,
ctx.subscriptions
);
vscode.workspace.onDidChangeTextDocument(
this.onDidChangeTextDocument,
this,
ctx.subscriptions
);
vscode.window.onDidChangeVisibleTextEditors(
this.onDidChangeVisibleTextEditors,
this,
ctx.subscriptions
);
ctx.pushCleanup(this);
}
@ -48,7 +60,10 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
}
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) {
if (
this.rustEditor &&
event.document.uri.toString() === this.rustEditor.document.uri.toString()
) {
this.rust2Ast.reset();
}
}
@ -68,7 +83,9 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
}
private findAstTextEditor(): undefined | vscode.TextEditor {
return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === 'rust-analyzer');
return vscode.window.visibleTextEditors.find(
(it) => it.document.uri.scheme === "rust-analyzer"
);
}
private setRustEditor(newRustEditor: undefined | RustEditor) {
@ -80,13 +97,20 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
}
// additional positional params are omitted
provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult<vscode.DefinitionLink[]> {
if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return;
provideDefinition(
doc: vscode.TextDocument,
pos: vscode.Position
): vscode.ProviderResult<vscode.DefinitionLink[]> {
if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) {
return;
}
const astEditor = this.findAstTextEditor();
if (!astEditor) return;
const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos));
const rust2AstRanges = this.rust2Ast
.get()
?.find(([rustRange, _]) => rustRange.contains(pos));
if (!rust2AstRanges) return;
const [rustFileRange, astFileRange] = rust2AstRanges;
@ -94,16 +118,21 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
astEditor.revealRange(astFileRange);
astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end);
return [{
targetRange: astFileRange,
targetUri: astEditor.document.uri,
originSelectionRange: rustFileRange,
targetSelectionRange: astFileRange,
}];
return [
{
targetRange: astFileRange,
targetUri: astEditor.document.uri,
originSelectionRange: rustFileRange,
targetSelectionRange: astFileRange,
},
];
}
// additional positional params are omitted
provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> {
provideHover(
doc: vscode.TextDocument,
hoverPosition: vscode.Position
): vscode.ProviderResult<vscode.Hover> {
if (!this.rustEditor) return;
const astFileLine = doc.lineAt(hoverPosition.line);
@ -127,13 +156,14 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
return new vscode.Range(begin, end);
}
private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range {
private parseRustTextRange(
doc: vscode.TextDocument,
astLine: string
): undefined | vscode.Range {
const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine);
if (!parsedRange) return;
const [begin, end] = parsedRange
.slice(1)
.map(off => this.positionAt(doc, +off));
const [begin, end] = parsedRange.slice(1).map((off) => this.positionAt(doc, +off));
return new vscode.Range(begin, end);
}
@ -173,7 +203,7 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
class Lazy<T> {
val: undefined | T;
constructor(private readonly compute: () => undefined | T) { }
constructor(private readonly compute: () => undefined | T) {}
get() {
return this.val ?? (this.val = this.compute());

View file

@ -1,39 +1,47 @@
import * as lc from 'vscode-languageclient/node';
import * as vscode from 'vscode';
import * as ra from '../src/lsp_ext';
import * as Is from 'vscode-languageclient/lib/common/utils/is';
import { assert } from './util';
import { WorkspaceEdit } from 'vscode';
import { Workspace } from './ctx';
import { updateConfig } from './config';
import { substituteVariablesInEnv } from './config';
import * as lc from "vscode-languageclient/node";
import * as vscode from "vscode";
import * as ra from "../src/lsp_ext";
import * as Is from "vscode-languageclient/lib/common/utils/is";
import { assert } from "./util";
import { WorkspaceEdit } from "vscode";
import { Workspace } from "./ctx";
import { updateConfig } from "./config";
import { substituteVariablesInEnv } from "./config";
export interface Env {
[name: string]: string;
}
function renderCommand(cmd: ra.CommandLink) {
return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip}')`;
return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(
JSON.stringify(cmd.arguments)
)} '${cmd.tooltip}')`;
}
function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString {
const text = actions.map(group =>
(group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ')
).join('___');
const text = actions
.map(
(group) =>
(group.title ? group.title + " " : "") +
group.commands.map(renderCommand).join(" | ")
)
.join("___");
const result = new vscode.MarkdownString(text);
result.isTrusted = true;
return result;
}
export async function createClient(serverPath: string, workspace: Workspace, extraEnv: Env): Promise<lc.LanguageClient> {
export async function createClient(
serverPath: string,
workspace: Workspace,
extraEnv: Env
): Promise<lc.LanguageClient> {
// '.' Is the fallback if no folder is open
// TODO?: Workspace folders support Uri's (eg: file://test.txt).
// It might be a good idea to test if the uri points to a file.
const newEnv = substituteVariablesInEnv(Object.assign(
{}, process.env, extraEnv
));
const newEnv = substituteVariablesInEnv(Object.assign({}, process.env, extraEnv));
const run: lc.Executable = {
command: serverPath,
options: { env: newEnv },
@ -43,137 +51,176 @@ export async function createClient(serverPath: string, workspace: Workspace, ext
debug: run,
};
const traceOutputChannel = vscode.window.createOutputChannel(
'Rust Analyzer Language Server Trace',
"Rust Analyzer Language Server Trace"
);
let initializationOptions = vscode.workspace.getConfiguration("rust-analyzer");
// Update outdated user configs
await updateConfig(initializationOptions).catch(err => {
await updateConfig(initializationOptions).catch((err) => {
void vscode.window.showErrorMessage(`Failed updating old config keys: ${err.message}`);
});
if (workspace.kind === "Detached Files") {
initializationOptions = { "detachedFiles": workspace.files.map(file => file.uri.fsPath), ...initializationOptions };
initializationOptions = {
detachedFiles: workspace.files.map((file) => file.uri.fsPath),
...initializationOptions,
};
}
const clientOptions: lc.LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'rust' }],
documentSelector: [{ scheme: "file", language: "rust" }],
initializationOptions,
diagnosticCollectionName: "rustc",
traceOutputChannel,
middleware: {
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) {
async provideHover(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
_next: lc.ProvideHoverSignature
) {
const editor = vscode.window.activeTextEditor;
const positionOrRange = editor?.selection?.contains(position) ? client.code2ProtocolConverter.asRange(editor.selection) : client.code2ProtocolConverter.asPosition(position);
return client.sendRequest(ra.hover, {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
position: positionOrRange
}, token).then(
(result) => {
const hover =
client.protocol2CodeConverter.asHover(result);
if (hover) {
const actions = (<any>result).actions;
if (actions) {
hover.contents.push(renderHoverActions(actions));
const positionOrRange = editor?.selection?.contains(position)
? client.code2ProtocolConverter.asRange(editor.selection)
: client.code2ProtocolConverter.asPosition(position);
return client
.sendRequest(
ra.hover,
{
textDocument:
client.code2ProtocolConverter.asTextDocumentIdentifier(document),
position: positionOrRange,
},
token
)
.then(
(result) => {
const hover = client.protocol2CodeConverter.asHover(result);
if (hover) {
const actions = (<any>result).actions;
if (actions) {
hover.contents.push(renderHoverActions(actions));
}
}
return hover;
},
(error) => {
client.handleFailedRequest(lc.HoverRequest.type, token, error, null);
return Promise.resolve(null);
}
return hover;
},
(error) => {
client.handleFailedRequest(
lc.HoverRequest.type,
token,
error,
null
);
return Promise.resolve(null);
}
);
);
},
// Using custom handling of CodeActions to support action groups and snippet edits.
// Note that this means we have to re-implement lazy edit resolving ourselves as well.
async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
async provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
context: vscode.CodeActionContext,
token: vscode.CancellationToken,
_next: lc.ProvideCodeActionsSignature
) {
const params: lc.CodeActionParams = {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
range: client.code2ProtocolConverter.asRange(range),
context: await client.code2ProtocolConverter.asCodeActionContext(context, token)
context: await client.code2ProtocolConverter.asCodeActionContext(
context,
token
),
};
return client.sendRequest(lc.CodeActionRequest.type, params, token).then(async (values) => {
if (values === null) return undefined;
const result: (vscode.CodeAction | vscode.Command)[] = [];
const groups = new Map<string, { index: number; items: vscode.CodeAction[] }>();
for (const item of values) {
// In our case we expect to get code edits only from diagnostics
if (lc.CodeAction.is(item)) {
assert(!item.command, "We don't expect to receive commands in CodeActions");
const action = await client.protocol2CodeConverter.asCodeAction(item, token);
result.push(action);
continue;
}
assert(isCodeActionWithoutEditsAndCommands(item), "We don't expect edits or commands here");
const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind);
const action = new vscode.CodeAction(item.title, kind);
const group = (item as any).group;
action.command = {
command: "rust-analyzer.resolveCodeAction",
title: item.title,
arguments: [item],
};
// Set a dummy edit, so that VS Code doesn't try to resolve this.
action.edit = new WorkspaceEdit();
if (group) {
let entry = groups.get(group);
if (!entry) {
entry = { index: result.length, items: [] };
groups.set(group, entry);
return client.sendRequest(lc.CodeActionRequest.type, params, token).then(
async (values) => {
if (values === null) return undefined;
const result: (vscode.CodeAction | vscode.Command)[] = [];
const groups = new Map<
string,
{ index: number; items: vscode.CodeAction[] }
>();
for (const item of values) {
// In our case we expect to get code edits only from diagnostics
if (lc.CodeAction.is(item)) {
assert(
!item.command,
"We don't expect to receive commands in CodeActions"
);
const action = await client.protocol2CodeConverter.asCodeAction(
item,
token
);
result.push(action);
continue;
}
entry.items.push(action);
} else {
result.push(action);
}
}
for (const [group, { index, items }] of groups) {
if (items.length === 1) {
result[index] = items[0];
} else {
const action = new vscode.CodeAction(group);
action.kind = items[0].kind;
assert(
isCodeActionWithoutEditsAndCommands(item),
"We don't expect edits or commands here"
);
const kind = client.protocol2CodeConverter.asCodeActionKind(
(item as any).kind
);
const action = new vscode.CodeAction(item.title, kind);
const group = (item as any).group;
action.command = {
command: "rust-analyzer.applyActionGroup",
title: "",
arguments: [items.map((item) => {
return { label: item.title, arguments: item.command!.arguments![0] };
})],
command: "rust-analyzer.resolveCodeAction",
title: item.title,
arguments: [item],
};
// Set a dummy edit, so that VS Code doesn't try to resolve this.
action.edit = new WorkspaceEdit();
result[index] = action;
if (group) {
let entry = groups.get(group);
if (!entry) {
entry = { index: result.length, items: [] };
groups.set(group, entry);
result.push(action);
}
entry.items.push(action);
} else {
result.push(action);
}
}
}
return result;
},
for (const [group, { index, items }] of groups) {
if (items.length === 1) {
result[index] = items[0];
} else {
const action = new vscode.CodeAction(group);
action.kind = items[0].kind;
action.command = {
command: "rust-analyzer.applyActionGroup",
title: "",
arguments: [
items.map((item) => {
return {
label: item.title,
arguments: item.command!.arguments![0],
};
}),
],
};
// Set a dummy edit, so that VS Code doesn't try to resolve this.
action.edit = new WorkspaceEdit();
result[index] = action;
}
}
return result;
},
(_error) => undefined
);
}
},
},
markdown: {
supportHtml: true,
}
},
};
const client = new lc.LanguageClient(
'rust-analyzer',
'Rust Analyzer Language Server',
"rust-analyzer",
"Rust Analyzer Language Server",
serverOptions,
clientOptions,
clientOptions
);
// To turn on all proposed features use: client.registerProposedFeatures();
@ -196,20 +243,26 @@ class ExperimentalFeatures implements lc.StaticFeature {
"rust-analyzer.showReferences",
"rust-analyzer.gotoLocation",
"editor.action.triggerParameterHints",
]
],
};
capabilities.experimental = caps;
}
initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
}
dispose(): void {
}
initialize(
_capabilities: lc.ServerCapabilities<any>,
_documentSelector: lc.DocumentSelector | undefined
): void {}
dispose(): void {}
}
function isCodeActionWithoutEditsAndCommands(value: any): boolean {
const candidate: lc.CodeAction = value;
return candidate && Is.string(candidate.title) &&
(candidate.diagnostics === void 0 || Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) &&
return (
candidate &&
Is.string(candidate.title) &&
(candidate.diagnostics === void 0 ||
Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) &&
(candidate.kind === void 0 || Is.string(candidate.kind)) &&
(candidate.edit === void 0 && candidate.command === void 0);
candidate.edit === void 0 &&
candidate.command === void 0
);
}

View file

@ -1,32 +1,33 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from './lsp_ext';
import * as path from 'path';
import * as vscode from "vscode";
import * as lc from "vscode-languageclient";
import * as ra from "./lsp_ext";
import * as path from "path";
import { Ctx, Cmd } from './ctx';
import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
import { spawnSync } from 'child_process';
import { RunnableQuickPick, selectRunnable, createTask, createArgs } from './run';
import { AstInspector } from './ast_inspector';
import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from './util';
import { startDebugSession, makeDebugConfig } from './debug';
import { LanguageClient } from 'vscode-languageclient/node';
import { Ctx, Cmd } from "./ctx";
import { applySnippetWorkspaceEdit, applySnippetTextEdits } from "./snippets";
import { spawnSync } from "child_process";
import { RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run";
import { AstInspector } from "./ast_inspector";
import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from "./util";
import { startDebugSession, makeDebugConfig } from "./debug";
import { LanguageClient } from "vscode-languageclient/node";
export * from './ast_inspector';
export * from './run';
export * from "./ast_inspector";
export * from "./run";
export function analyzerStatus(ctx: Ctx): Cmd {
const tdcp = new class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse('rust-analyzer-status://status');
const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer-status://status");
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
if (!vscode.window.activeTextEditor) return '';
if (!vscode.window.activeTextEditor) return "";
const params: ra.AnalyzerStatusParams = {};
const doc = ctx.activeRustEditor?.document;
if (doc != null) {
params.textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(doc);
params.textDocument =
ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(doc);
}
return ctx.client.sendRequest(ra.analyzerStatus, params);
}
@ -34,48 +35,42 @@ export function analyzerStatus(ctx: Ctx): Cmd {
get onDidChange(): vscode.Event<vscode.Uri> {
return this.eventEmitter.event;
}
}();
})();
ctx.pushCleanup(
vscode.workspace.registerTextDocumentContentProvider(
'rust-analyzer-status',
tdcp,
),
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-status", tdcp)
);
return async () => {
const document = await vscode.workspace.openTextDocument(tdcp.uri);
tdcp.eventEmitter.fire(tdcp.uri);
void await vscode.window.showTextDocument(document, {
void (await vscode.window.showTextDocument(document, {
viewColumn: vscode.ViewColumn.Two,
preserveFocus: true
});
preserveFocus: true,
}));
};
}
export function memoryUsage(ctx: Ctx): Cmd {
const tdcp = new class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse('rust-analyzer-memory://memory');
const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer-memory://memory");
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
if (!vscode.window.activeTextEditor) return '';
if (!vscode.window.activeTextEditor) return "";
return ctx.client.sendRequest(ra.memoryUsage).then((mem: any) => {
return 'Per-query memory usage:\n' + mem + '\n(note: database has been cleared)';
return "Per-query memory usage:\n" + mem + "\n(note: database has been cleared)";
});
}
get onDidChange(): vscode.Event<vscode.Uri> {
return this.eventEmitter.event;
}
}();
})();
ctx.pushCleanup(
vscode.workspace.registerTextDocumentContentProvider(
'rust-analyzer-memory',
tdcp,
),
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp)
);
return async () => {
@ -101,15 +96,15 @@ export function matchingBrace(ctx: Ctx): Cmd {
if (!editor || !client) return;
const response = await client.sendRequest(ra.matchingBrace, {
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
positions: editor.selections.map(s =>
client.code2ProtocolConverter.asPosition(s.active),
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
editor.document
),
positions: editor.selections.map((s) =>
client.code2ProtocolConverter.asPosition(s.active)
),
});
editor.selections = editor.selections.map((sel, idx) => {
const active = client.protocol2CodeConverter.asPosition(
response[idx],
);
const active = client.protocol2CodeConverter.asPosition(response[idx]);
const anchor = sel.isEmpty ? active : sel.anchor;
return new vscode.Selection(anchor, active);
});
@ -125,7 +120,9 @@ export function joinLines(ctx: Ctx): Cmd {
const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
editor.document
),
});
await editor.edit(async (builder) => {
(await client.protocol2CodeConverter.asTextEdits(items)).forEach((edit: any) => {
@ -151,8 +148,10 @@ export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd {
const lcEdits = await client.sendRequest(ra.moveItem, {
range: client.code2ProtocolConverter.asRange(editor.selection),
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
direction
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
editor.document
),
direction,
});
if (!lcEdits) return;
@ -169,15 +168,17 @@ export function onEnter(ctx: Ctx): Cmd {
if (!editor || !client) return false;
const lcEdits = await client.sendRequest(ra.onEnter, {
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
position: client.code2ProtocolConverter.asPosition(
editor.selection.active,
),
}).catch((_error: any) => {
// client.handleFailedRequest(OnEnterRequest.type, error, null);
return null;
});
const lcEdits = await client
.sendRequest(ra.onEnter, {
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
editor.document
),
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
})
.catch((_error: any) => {
// client.handleFailedRequest(OnEnterRequest.type, error, null);
return null;
});
if (!lcEdits) return false;
const edits = await client.protocol2CodeConverter.asTextEdits(lcEdits);
@ -188,7 +189,7 @@ export function onEnter(ctx: Ctx): Cmd {
return async () => {
if (await handleKeypress()) return;
await vscode.commands.executeCommand('default:type', { text: '\n' });
await vscode.commands.executeCommand("default:type", { text: "\n" });
};
}
@ -200,10 +201,10 @@ export function parentModule(ctx: Ctx): Cmd {
if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
const locations = await client.sendRequest(ra.parentModule, {
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
position: client.code2ProtocolConverter.asPosition(
editor.selection.active,
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
editor.document
),
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
});
if (!locations) return;
@ -220,7 +221,12 @@ export function parentModule(ctx: Ctx): Cmd {
} else {
const uri = editor.document.uri.toString();
const position = client.code2ProtocolConverter.asPosition(editor.selection.active);
await showReferencesImpl(client, uri, position, locations.map(loc => lc.Location.create(loc.targetUri, loc.targetRange)));
await showReferencesImpl(
client,
uri,
position,
locations.map((loc) => lc.Location.create(loc.targetUri, loc.targetRange))
);
}
};
}
@ -232,7 +238,9 @@ export function openCargoToml(ctx: Ctx): Cmd {
if (!editor || !client) return;
const response = await client.sendRequest(ra.openCargoToml, {
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
editor.document
),
});
if (!response) return;
@ -254,7 +262,9 @@ export function ssr(ctx: Ctx): Cmd {
const position = editor.selection.active;
const selections = editor.selections;
const textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document);
const textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
editor.document
);
const options: vscode.InputBoxOptions = {
value: "() ==>> ()",
@ -262,28 +272,41 @@ export function ssr(ctx: Ctx): Cmd {
validateInput: async (x: string) => {
try {
await client.sendRequest(ra.ssr, {
query: x, parseOnly: true, textDocument, position, selections,
query: x,
parseOnly: true,
textDocument,
position,
selections,
});
} catch (e) {
return e.toString();
}
return null;
}
},
};
const request = await vscode.window.showInputBox(options);
if (!request) return;
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Structured search replace in progress...",
cancellable: false,
}, async (_progress, token) => {
const edit = await client.sendRequest(ra.ssr, {
query: request, parseOnly: false, textDocument, position, selections,
});
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: "Structured search replace in progress...",
cancellable: false,
},
async (_progress, token) => {
const edit = await client.sendRequest(ra.ssr, {
query: request,
parseOnly: false,
textDocument,
position,
selections,
});
await vscode.workspace.applyEdit(await client.protocol2CodeConverter.asWorkspaceEdit(edit, token));
});
await vscode.workspace.applyEdit(
await client.protocol2CodeConverter.asWorkspaceEdit(edit, token)
);
}
);
};
}
@ -292,17 +315,17 @@ export function serverVersion(ctx: Ctx): Cmd {
const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
const versionString = stdout.slice(`rust-analyzer `.length).trim();
void vscode.window.showInformationMessage(
`rust-analyzer version: ${versionString}`
);
void vscode.window.showInformationMessage(`rust-analyzer version: ${versionString}`);
};
}
export function toggleInlayHints(_ctx: Ctx): Cmd {
return async () => {
const config = vscode.workspace.getConfiguration("editor.inlayHints", { languageId: "rust" });
const config = vscode.workspace.getConfiguration("editor.inlayHints", {
languageId: "rust",
});
const value = !config.get("enabled");
await config.update('enabled', value, vscode.ConfigurationTarget.Global);
await config.update("enabled", value, vscode.ConfigurationTarget.Global);
};
}
@ -310,12 +333,20 @@ export function toggleInlayHints(_ctx: Ctx): Cmd {
//
// The contents of the file come from the `TextDocumentContentProvider`
export function syntaxTree(ctx: Ctx): Cmd {
const tdcp = new class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast');
const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer://syntaxtree/tree.rast");
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
constructor() {
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
vscode.workspace.onDidChangeTextDocument(
this.onDidChangeTextDocument,
this,
ctx.subscriptions
);
vscode.window.onDidChangeActiveTextEditor(
this.onDidChangeActiveTextEditor,
this,
ctx.subscriptions
);
}
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
@ -331,47 +362,51 @@ export function syntaxTree(ctx: Ctx): Cmd {
}
}
provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
provideTextDocumentContent(
uri: vscode.Uri,
ct: vscode.CancellationToken
): vscode.ProviderResult<string> {
const rustEditor = ctx.activeRustEditor;
if (!rustEditor) return '';
if (!rustEditor) return "";
// When the range based query is enabled we take the range of the selection
const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
? ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
: null;
const range =
uri.query === "range=true" && !rustEditor.selection.isEmpty
? ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
: null;
const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range };
return ctx.client.sendRequest(ra.syntaxTree, params, ct);
}
get onDidChange(): vscode.Event<vscode.Uri> {
return this.eventEmitter.event;
}
};
})();
void new AstInspector(ctx);
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
brackets: [["[", ")"]],
}));
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider("rust-analyzer", tdcp));
ctx.pushCleanup(
vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
brackets: [["[", ")"]],
})
);
return async () => {
const editor = vscode.window.activeTextEditor;
const rangeEnabled = !!editor && !editor.selection.isEmpty;
const uri = rangeEnabled
? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
: tdcp.uri;
const uri = rangeEnabled ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) : tdcp.uri;
const document = await vscode.workspace.openTextDocument(uri);
tdcp.eventEmitter.fire(uri);
void await vscode.window.showTextDocument(document, {
void (await vscode.window.showTextDocument(document, {
viewColumn: vscode.ViewColumn.Two,
preserveFocus: true
});
preserveFocus: true,
}));
};
}
@ -379,12 +414,20 @@ export function syntaxTree(ctx: Ctx): Cmd {
//
// The contents of the file come from the `TextDocumentContentProvider`
export function viewHir(ctx: Ctx): Cmd {
const tdcp = new class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse('rust-analyzer://viewHir/hir.txt');
const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer://viewHir/hir.txt");
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
constructor() {
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
vscode.workspace.onDidChangeTextDocument(
this.onDidChangeTextDocument,
this,
ctx.subscriptions
);
vscode.window.onDidChangeActiveTextEditor(
this.onDidChangeActiveTextEditor,
this,
ctx.subscriptions
);
}
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
@ -400,16 +443,19 @@ export function viewHir(ctx: Ctx): Cmd {
}
}
provideTextDocumentContent(_uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
provideTextDocumentContent(
_uri: vscode.Uri,
ct: vscode.CancellationToken
): vscode.ProviderResult<string> {
const rustEditor = ctx.activeRustEditor;
const client = ctx.client;
if (!rustEditor || !client) return '';
if (!rustEditor || !client) return "";
const params = {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(rustEditor.document),
position: client.code2ProtocolConverter.asPosition(
rustEditor.selection.active,
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
rustEditor.document
),
position: client.code2ProtocolConverter.asPosition(rustEditor.selection.active),
};
return client.sendRequest(ra.viewHir, params, ct);
}
@ -417,27 +463,35 @@ export function viewHir(ctx: Ctx): Cmd {
get onDidChange(): vscode.Event<vscode.Uri> {
return this.eventEmitter.event;
}
};
})();
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider("rust-analyzer", tdcp));
return async () => {
const document = await vscode.workspace.openTextDocument(tdcp.uri);
tdcp.eventEmitter.fire(tdcp.uri);
void await vscode.window.showTextDocument(document, {
void (await vscode.window.showTextDocument(document, {
viewColumn: vscode.ViewColumn.Two,
preserveFocus: true
});
preserveFocus: true,
}));
};
}
export function viewFileText(ctx: Ctx): Cmd {
const tdcp = new class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse('rust-analyzer://viewFileText/file.rs');
const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer://viewFileText/file.rs");
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
constructor() {
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
vscode.workspace.onDidChangeTextDocument(
this.onDidChangeTextDocument,
this,
ctx.subscriptions
);
vscode.window.onDidChangeActiveTextEditor(
this.onDidChangeActiveTextEditor,
this,
ctx.subscriptions
);
}
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
@ -453,39 +507,52 @@ export function viewFileText(ctx: Ctx): Cmd {
}
}
provideTextDocumentContent(_uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
provideTextDocumentContent(
_uri: vscode.Uri,
ct: vscode.CancellationToken
): vscode.ProviderResult<string> {
const rustEditor = ctx.activeRustEditor;
const client = ctx.client;
if (!rustEditor || !client) return '';
if (!rustEditor || !client) return "";
const params = client.code2ProtocolConverter.asTextDocumentIdentifier(rustEditor.document);
const params = client.code2ProtocolConverter.asTextDocumentIdentifier(
rustEditor.document
);
return client.sendRequest(ra.viewFileText, params, ct);
}
get onDidChange(): vscode.Event<vscode.Uri> {
return this.eventEmitter.event;
}
};
})();
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider("rust-analyzer", tdcp));
return async () => {
const document = await vscode.workspace.openTextDocument(tdcp.uri);
tdcp.eventEmitter.fire(tdcp.uri);
void await vscode.window.showTextDocument(document, {
void (await vscode.window.showTextDocument(document, {
viewColumn: vscode.ViewColumn.Two,
preserveFocus: true
});
preserveFocus: true,
}));
};
}
export function viewItemTree(ctx: Ctx): Cmd {
const tdcp = new class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse('rust-analyzer://viewItemTree/itemtree.rs');
const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer://viewItemTree/itemtree.rs");
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
constructor() {
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
vscode.workspace.onDidChangeTextDocument(
this.onDidChangeTextDocument,
this,
ctx.subscriptions
);
vscode.window.onDidChangeActiveTextEditor(
this.onDidChangeActiveTextEditor,
this,
ctx.subscriptions
);
}
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
@ -501,13 +568,18 @@ export function viewItemTree(ctx: Ctx): Cmd {
}
}
provideTextDocumentContent(_uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
provideTextDocumentContent(
_uri: vscode.Uri,
ct: vscode.CancellationToken
): vscode.ProviderResult<string> {
const rustEditor = ctx.activeRustEditor;
const client = ctx.client;
if (!rustEditor || !client) return '';
if (!rustEditor || !client) return "";
const params = {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(rustEditor.document),
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
rustEditor.document
),
};
return client.sendRequest(ra.viewItemTree, params, ct);
}
@ -515,17 +587,17 @@ export function viewItemTree(ctx: Ctx): Cmd {
get onDidChange(): vscode.Event<vscode.Uri> {
return this.eventEmitter.event;
}
};
})();
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider("rust-analyzer", tdcp));
return async () => {
const document = await vscode.workspace.openTextDocument(tdcp.uri);
tdcp.eventEmitter.fire(tdcp.uri);
void await vscode.window.showTextDocument(document, {
void (await vscode.window.showTextDocument(document, {
viewColumn: vscode.ViewColumn.Two,
preserveFocus: true
});
preserveFocus: true,
}));
};
}
@ -533,11 +605,16 @@ function crateGraph(ctx: Ctx, full: boolean): Cmd {
return async () => {
const nodeModulesPath = vscode.Uri.file(path.join(ctx.extensionPath, "node_modules"));
const panel = vscode.window.createWebviewPanel("rust-analyzer.crate-graph", "rust-analyzer crate graph", vscode.ViewColumn.Two, {
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [nodeModulesPath]
});
const panel = vscode.window.createWebviewPanel(
"rust-analyzer.crate-graph",
"rust-analyzer crate graph",
vscode.ViewColumn.Two,
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [nodeModulesPath],
}
);
const params = {
full: full,
};
@ -601,29 +678,31 @@ export function viewFullCrateGraph(ctx: Ctx): Cmd {
export function expandMacro(ctx: Ctx): Cmd {
function codeFormat(expanded: ra.ExpandedMacro): string {
let result = `// Recursive expansion of ${expanded.name}! macro\n`;
result += '// ' + '='.repeat(result.length - 3);
result += '\n\n';
result += "// " + "=".repeat(result.length - 3);
result += "\n\n";
result += expanded.expansion;
return result;
}
const tdcp = new class implements vscode.TextDocumentContentProvider {
uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs');
const tdcp = new (class implements vscode.TextDocumentContentProvider {
uri = vscode.Uri.parse("rust-analyzer://expandMacro/[EXPANSION].rs");
eventEmitter = new vscode.EventEmitter<vscode.Uri>();
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
const editor = vscode.window.activeTextEditor;
const client = ctx.client;
if (!editor || !client) return '';
if (!editor || !client) return "";
const position = editor.selection.active;
const expanded = await client.sendRequest(ra.expandMacro, {
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
editor.document
),
position,
});
if (expanded == null) return 'Not available';
if (expanded == null) return "Not available";
return codeFormat(expanded);
}
@ -631,23 +710,14 @@ export function expandMacro(ctx: Ctx): Cmd {
get onDidChange(): vscode.Event<vscode.Uri> {
return this.eventEmitter.event;
}
}();
})();
ctx.pushCleanup(
vscode.workspace.registerTextDocumentContentProvider(
'rust-analyzer',
tdcp,
),
);
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider("rust-analyzer", tdcp));
return async () => {
const document = await vscode.workspace.openTextDocument(tdcp.uri);
tdcp.eventEmitter.fire(tdcp.uri);
return vscode.window.showTextDocument(
document,
vscode.ViewColumn.Two,
true,
);
return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
};
}
@ -655,13 +725,18 @@ export function reloadWorkspace(ctx: Ctx): Cmd {
return async () => ctx.client.sendRequest(ra.reloadWorkspace);
}
async function showReferencesImpl(client: LanguageClient, uri: string, position: lc.Position, locations: lc.Location[]) {
async function showReferencesImpl(
client: LanguageClient,
uri: string,
position: lc.Position,
locations: lc.Location[]
) {
if (client) {
await vscode.commands.executeCommand(
'editor.action.showReferences',
"editor.action.showReferences",
vscode.Uri.parse(uri),
client.protocol2CodeConverter.asPosition(position),
locations.map(client.protocol2CodeConverter.asLocation),
locations.map(client.protocol2CodeConverter.asLocation)
);
}
}
@ -677,8 +752,8 @@ export function applyActionGroup(_ctx: Ctx): Cmd {
const selectedAction = await vscode.window.showQuickPick(actions);
if (!selectedAction) return;
await vscode.commands.executeCommand(
'rust-analyzer.resolveCodeAction',
selectedAction.arguments,
"rust-analyzer.resolveCodeAction",
selectedAction.arguments
);
};
}
@ -699,12 +774,11 @@ export function gotoLocation(ctx: Ctx): Cmd {
export function openDocs(ctx: Ctx): Cmd {
return async () => {
const client = ctx.client;
const editor = vscode.window.activeTextEditor;
if (!editor || !client) {
return;
};
}
const position = editor.selection.active;
const textDocument = { uri: editor.document.uri.toString() };
@ -715,7 +789,6 @@ export function openDocs(ctx: Ctx): Cmd {
await vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink));
}
};
}
export function resolveCodeAction(ctx: Ctx): Cmd {
@ -730,8 +803,13 @@ export function resolveCodeAction(ctx: Ctx): Cmd {
const edit = await client.protocol2CodeConverter.asWorkspaceEdit(itemEdit);
// filter out all text edits and recreate the WorkspaceEdit without them so we can apply
// snippet edits on our own
const lcFileSystemEdit = { ...itemEdit, documentChanges: itemEdit.documentChanges?.filter(change => "kind" in change) };
const fileSystemEdit = await client.protocol2CodeConverter.asWorkspaceEdit(lcFileSystemEdit);
const lcFileSystemEdit = {
...itemEdit,
documentChanges: itemEdit.documentChanges?.filter((change) => "kind" in change),
};
const fileSystemEdit = await client.protocol2CodeConverter.asWorkspaceEdit(
lcFileSystemEdit
);
await vscode.workspace.applyEdit(fileSystemEdit);
await applySnippetWorkspaceEdit(edit);
if (item.command != null) {
@ -753,7 +831,7 @@ export function run(ctx: Ctx): Cmd {
const item = await selectRunnable(ctx, prevRunnable);
if (!item) return;
item.detail = 'rerun';
item.detail = "rerun";
prevRunnable = item;
const task = await createTask(item.runnable, ctx.config);
return await vscode.tasks.executeTask(task);
@ -767,29 +845,33 @@ export function peekTests(ctx: Ctx): Cmd {
const editor = ctx.activeRustEditor;
if (!editor || !client) return;
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Looking for tests...",
cancellable: false,
}, async (_progress, _token) => {
const uri = editor.document.uri.toString();
const position = client.code2ProtocolConverter.asPosition(
editor.selection.active,
);
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: "Looking for tests...",
cancellable: false,
},
async (_progress, _token) => {
const uri = editor.document.uri.toString();
const position = client.code2ProtocolConverter.asPosition(editor.selection.active);
const tests = await client.sendRequest(ra.relatedTests, {
textDocument: { uri: uri },
position: position,
});
const locations: lc.Location[] = tests.map(it =>
lc.Location.create(it.runnable.location!.targetUri, it.runnable.location!.targetSelectionRange));
const tests = await client.sendRequest(ra.relatedTests, {
textDocument: { uri: uri },
position: position,
});
const locations: lc.Location[] = tests.map((it) =>
lc.Location.create(
it.runnable.location!.targetUri,
it.runnable.location!.targetSelectionRange
)
);
await showReferencesImpl(client, uri, position, locations);
});
await showReferencesImpl(client, uri, position, locations);
}
);
};
}
export function runSingle(ctx: Ctx): Cmd {
return async (runnable: ra.Runnable) => {
const editor = ctx.activeRustEditor;
@ -826,7 +908,7 @@ export function debug(ctx: Ctx): Cmd {
const item = await selectRunnable(ctx, prevDebuggee, true);
if (!item) return;
item.detail = 'restart';
item.detail = "restart";
prevDebuggee = item;
return await startDebugSession(ctx, item.runnable);
};

View file

@ -1,13 +1,16 @@
import path = require('path');
import * as vscode from 'vscode';
import { Env } from './client';
import path = require("path");
import * as vscode from "vscode";
import { Env } from "./client";
import { log } from "./util";
export type UpdatesChannel = "stable" | "nightly";
const NIGHTLY_TAG = "nightly";
export type RunnableEnvCfg = undefined | Record<string, string> | { mask?: string; env: Record<string, string> }[];
export type RunnableEnvCfg =
| undefined
| Record<string, string>
| { mask?: string; env: Record<string, string> }[];
export class Config {
readonly extensionId = "rust-lang.rust-analyzer";
@ -20,8 +23,7 @@ export class Config {
"procMacro",
"files",
"lens", // works as lens.*
]
.map(opt => `${this.rootSection}.${opt}`);
].map((opt) => `${this.rootSection}.${opt}`);
readonly package: {
version: string;
@ -33,7 +35,11 @@ export class Config {
constructor(ctx: vscode.ExtensionContext) {
this.globalStorageUri = ctx.globalStorageUri;
vscode.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, ctx.subscriptions);
vscode.workspace.onDidChangeConfiguration(
this.onDidChangeConfiguration,
this,
ctx.subscriptions
);
this.refreshLogging();
}
@ -48,8 +54,8 @@ export class Config {
private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) {
this.refreshLogging();
const requiresReloadOpt = this.requiresReloadOpts.find(
opt => event.affectsConfiguration(opt)
const requiresReloadOpt = this.requiresReloadOpts.find((opt) =>
event.affectsConfiguration(opt)
);
if (!requiresReloadOpt) return;
@ -94,8 +100,12 @@ export class Config {
get serverPath() {
return this.get<null | string>("server.path") ?? this.get<null | string>("serverPath");
}
get serverExtraEnv() { return this.get<Env | null>("server.extraEnv") ?? {}; }
get traceExtension() { return this.get<boolean>("trace.extension"); }
get serverExtraEnv() {
return this.get<Env | null>("server.extraEnv") ?? {};
}
get traceExtension() {
return this.get<boolean>("trace.extension");
}
get cargoRunner() {
return this.get<string | undefined>("cargoRunner");
@ -109,7 +119,8 @@ export class Config {
let sourceFileMap = this.get<Record<string, string> | "auto">("debug.sourceFileMap");
if (sourceFileMap !== "auto") {
// "/rustc/<id>" used by suggestions only.
const { ["/rustc/<id>"]: _, ...trimmed } = this.get<Record<string, string>>("debug.sourceFileMap");
const { ["/rustc/<id>"]: _, ...trimmed } =
this.get<Record<string, string>>("debug.sourceFileMap");
sourceFileMap = trimmed;
}
@ -117,7 +128,7 @@ export class Config {
engine: this.get<string>("debug.engine"),
engineSettings: this.get<object>("debug.engineSettings"),
openDebugPane: this.get<boolean>("debug.openDebugPane"),
sourceFileMap: sourceFileMap
sourceFileMap: sourceFileMap,
};
}
@ -139,57 +150,69 @@ export class Config {
export async function updateConfig(config: vscode.WorkspaceConfiguration) {
const renames = [
["assist.allowMergingIntoGlobImports", "imports.merge.glob",],
["assist.exprFillDefault", "assist.expressionFillDefault",],
["assist.importEnforceGranularity", "imports.granularity.enforce",],
["assist.importGranularity", "imports.granularity.group",],
["assist.importMergeBehavior", "imports.granularity.group",],
["assist.importMergeBehaviour", "imports.granularity.group",],
["assist.importGroup", "imports.group.enable",],
["assist.importPrefix", "imports.prefix",],
["primeCaches.enable", "cachePriming.enable",],
["cache.warmup", "cachePriming.enable",],
["cargo.loadOutDirsFromCheck", "cargo.buildScripts.enable",],
["cargo.runBuildScripts", "cargo.buildScripts.enable",],
["cargo.runBuildScriptsCommand", "cargo.buildScripts.overrideCommand",],
["cargo.useRustcWrapperForBuildScripts", "cargo.buildScripts.useRustcWrapper",],
["completion.snippets", "completion.snippets.custom",],
["diagnostics.enableExperimental", "diagnostics.experimental.enable",],
["experimental.procAttrMacros", "procMacro.attributes.enable",],
["highlighting.strings", "semanticHighlighting.strings.enable",],
["highlightRelated.breakPoints", "highlightRelated.breakPoints.enable",],
["highlightRelated.exitPoints", "highlightRelated.exitPoints.enable",],
["highlightRelated.yieldPoints", "highlightRelated.yieldPoints.enable",],
["highlightRelated.references", "highlightRelated.references.enable",],
["hover.documentation", "hover.documentation.enable",],
["hover.linksInHover", "hover.links.enable",],
["hoverActions.linksInHover", "hover.links.enable",],
["hoverActions.debug", "hover.actions.debug.enable",],
["hoverActions.enable", "hover.actions.enable.enable",],
["hoverActions.gotoTypeDef", "hover.actions.gotoTypeDef.enable",],
["hoverActions.implementations", "hover.actions.implementations.enable",],
["hoverActions.references", "hover.actions.references.enable",],
["hoverActions.run", "hover.actions.run.enable",],
["inlayHints.chainingHints", "inlayHints.chainingHints.enable",],
["inlayHints.closureReturnTypeHints", "inlayHints.closureReturnTypeHints.enable",],
["inlayHints.hideNamedConstructorHints", "inlayHints.typeHints.hideNamedConstructor",],
["inlayHints.parameterHints", "inlayHints.parameterHints.enable",],
["inlayHints.reborrowHints", "inlayHints.reborrowHints.enable",],
["inlayHints.typeHints", "inlayHints.typeHints.enable",],
["lruCapacity", "lru.capacity",],
["runnables.cargoExtraArgs", "runnables.extraArgs",],
["runnables.overrideCargo", "runnables.command",],
["rustcSource", "rustc.source",],
["rustfmt.enableRangeFormatting", "rustfmt.rangeFormatting.enable"]
["assist.allowMergingIntoGlobImports", "imports.merge.glob"],
["assist.exprFillDefault", "assist.expressionFillDefault"],
["assist.importEnforceGranularity", "imports.granularity.enforce"],
["assist.importGranularity", "imports.granularity.group"],
["assist.importMergeBehavior", "imports.granularity.group"],
["assist.importMergeBehaviour", "imports.granularity.group"],
["assist.importGroup", "imports.group.enable"],
["assist.importPrefix", "imports.prefix"],
["primeCaches.enable", "cachePriming.enable"],
["cache.warmup", "cachePriming.enable"],
["cargo.loadOutDirsFromCheck", "cargo.buildScripts.enable"],
["cargo.runBuildScripts", "cargo.buildScripts.enable"],
["cargo.runBuildScriptsCommand", "cargo.buildScripts.overrideCommand"],
["cargo.useRustcWrapperForBuildScripts", "cargo.buildScripts.useRustcWrapper"],
["completion.snippets", "completion.snippets.custom"],
["diagnostics.enableExperimental", "diagnostics.experimental.enable"],
["experimental.procAttrMacros", "procMacro.attributes.enable"],
["highlighting.strings", "semanticHighlighting.strings.enable"],
["highlightRelated.breakPoints", "highlightRelated.breakPoints.enable"],
["highlightRelated.exitPoints", "highlightRelated.exitPoints.enable"],
["highlightRelated.yieldPoints", "highlightRelated.yieldPoints.enable"],
["highlightRelated.references", "highlightRelated.references.enable"],
["hover.documentation", "hover.documentation.enable"],
["hover.linksInHover", "hover.links.enable"],
["hoverActions.linksInHover", "hover.links.enable"],
["hoverActions.debug", "hover.actions.debug.enable"],
["hoverActions.enable", "hover.actions.enable.enable"],
["hoverActions.gotoTypeDef", "hover.actions.gotoTypeDef.enable"],
["hoverActions.implementations", "hover.actions.implementations.enable"],
["hoverActions.references", "hover.actions.references.enable"],
["hoverActions.run", "hover.actions.run.enable"],
["inlayHints.chainingHints", "inlayHints.chainingHints.enable"],
["inlayHints.closureReturnTypeHints", "inlayHints.closureReturnTypeHints.enable"],
["inlayHints.hideNamedConstructorHints", "inlayHints.typeHints.hideNamedConstructor"],
["inlayHints.parameterHints", "inlayHints.parameterHints.enable"],
["inlayHints.reborrowHints", "inlayHints.reborrowHints.enable"],
["inlayHints.typeHints", "inlayHints.typeHints.enable"],
["lruCapacity", "lru.capacity"],
["runnables.cargoExtraArgs", "runnables.extraArgs"],
["runnables.overrideCargo", "runnables.command"],
["rustcSource", "rustc.source"],
["rustfmt.enableRangeFormatting", "rustfmt.rangeFormatting.enable"],
];
for (const [oldKey, newKey] of renames) {
const inspect = config.inspect(oldKey);
if (inspect !== undefined) {
const valMatrix = [
{ val: inspect.globalValue, langVal: inspect.globalLanguageValue, target: vscode.ConfigurationTarget.Global },
{ val: inspect.workspaceFolderValue, langVal: inspect.workspaceFolderLanguageValue, target: vscode.ConfigurationTarget.WorkspaceFolder },
{ val: inspect.workspaceValue, langVal: inspect.workspaceLanguageValue, target: vscode.ConfigurationTarget.Workspace }
{
val: inspect.globalValue,
langVal: inspect.globalLanguageValue,
target: vscode.ConfigurationTarget.Global,
},
{
val: inspect.workspaceFolderValue,
langVal: inspect.workspaceFolderLanguageValue,
target: vscode.ConfigurationTarget.WorkspaceFolder,
},
{
val: inspect.workspaceValue,
langVal: inspect.workspaceLanguageValue,
target: vscode.ConfigurationTarget.Workspace,
},
];
for (const { val, langVal, target } of valMatrix) {
const pred = (val: unknown) => {
@ -197,7 +220,14 @@ export async function updateConfig(config: vscode.WorkspaceConfiguration) {
// that means on the next run we would find these again, but as objects with
// these properties causing us to destroy the config
// so filter those already updated ones out
return val !== undefined && !(typeof val === "object" && val !== null && (val.hasOwnProperty("enable") || val.hasOwnProperty("custom")));
return (
val !== undefined &&
!(
typeof val === "object" &&
val !== null &&
(val.hasOwnProperty("enable") || val.hasOwnProperty("custom"))
)
);
};
if (pred(val)) {
await config.update(newKey, val, target, false);
@ -216,48 +246,50 @@ export function substituteVariablesInEnv(env: Env): Env {
const missingDeps = new Set<string>();
// vscode uses `env:ENV_NAME` for env vars resolution, and it's easier
// to follow the same convention for our dependency tracking
const definedEnvKeys = new Set(Object.keys(env).map(key => `env:${key}`));
const envWithDeps = Object.fromEntries(Object.entries(env).map(([key, value]) => {
const deps = new Set<string>();
const depRe = new RegExp(/\${(?<depName>.+?)}/g);
let match = undefined;
while ((match = depRe.exec(value))) {
const depName = match.groups!.depName;
deps.add(depName);
// `depName` at this point can have a form of `expression` or
// `prefix:expression`
if (!definedEnvKeys.has(depName)) {
missingDeps.add(depName);
const definedEnvKeys = new Set(Object.keys(env).map((key) => `env:${key}`));
const envWithDeps = Object.fromEntries(
Object.entries(env).map(([key, value]) => {
const deps = new Set<string>();
const depRe = new RegExp(/\${(?<depName>.+?)}/g);
let match = undefined;
while ((match = depRe.exec(value))) {
const depName = match.groups!.depName;
deps.add(depName);
// `depName` at this point can have a form of `expression` or
// `prefix:expression`
if (!definedEnvKeys.has(depName)) {
missingDeps.add(depName);
}
}
}
return [`env:${key}`, { deps: [...deps], value }];
}));
return [`env:${key}`, { deps: [...deps], value }];
})
);
const resolved = new Set<string>();
for (const dep of missingDeps) {
const match = /(?<prefix>.*?):(?<body>.+)/.exec(dep);
if (match) {
const { prefix, body } = match.groups!;
if (prefix === 'env') {
if (prefix === "env") {
const envName = body;
envWithDeps[dep] = {
value: process.env[envName] ?? '',
deps: []
value: process.env[envName] ?? "",
deps: [],
};
resolved.add(dep);
} else {
// we can't handle other prefixes at the moment
// leave values as is, but still mark them as resolved
envWithDeps[dep] = {
value: '${' + dep + '}',
deps: []
value: "${" + dep + "}",
deps: [],
};
resolved.add(dep);
}
} else {
envWithDeps[dep] = {
value: computeVscodeVar(dep),
deps: []
deps: [],
};
}
}
@ -267,11 +299,13 @@ export function substituteVariablesInEnv(env: Env): Env {
do {
leftToResolveSize = toResolve.size;
for (const key of toResolve) {
if (envWithDeps[key].deps.every(dep => resolved.has(dep))) {
if (envWithDeps[key].deps.every((dep) => resolved.has(dep))) {
envWithDeps[key].value = envWithDeps[key].value.replace(
/\${(?<depName>.+?)}/g, (_wholeMatch, depName) => {
/\${(?<depName>.+?)}/g,
(_wholeMatch, depName) => {
return envWithDeps[depName].value;
});
}
);
resolved.add(key);
toResolve.delete(key);
}
@ -302,16 +336,16 @@ function computeVscodeVar(varName: string): string {
return folders[0].uri.fsPath;
} else {
// no workspace opened
return '';
return "";
}
},
workspaceFolderBasename: () => {
const workspaceFolder = computeVscodeVar('workspaceFolder');
const workspaceFolder = computeVscodeVar("workspaceFolder");
if (workspaceFolder) {
return path.basename(workspaceFolder);
} else {
return '';
return "";
}
},
@ -323,13 +357,13 @@ function computeVscodeVar(varName: string): string {
// https://github.com/microsoft/vscode/blob/29eb316bb9f154b7870eb5204ec7f2e7cf649bec/src/vs/server/node/remoteTerminalChannel.ts#L56
execPath: () => process.env.VSCODE_EXEC_PATH ?? process.execPath,
pathSeparator: () => path.sep
pathSeparator: () => path.sep,
};
if (varName in supportedVariables) {
return supportedVariables[varName]();
} else {
// can't resolve, keep the expression as is
return '${' + varName + '}';
return "${" + varName + "}";
}
}

View file

@ -1,20 +1,20 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient/node';
import * as ra from './lsp_ext';
import * as vscode from "vscode";
import * as lc from "vscode-languageclient/node";
import * as ra from "./lsp_ext";
import { Config } from './config';
import { createClient } from './client';
import { isRustEditor, RustEditor } from './util';
import { ServerStatusParams } from './lsp_ext';
import { Config } from "./config";
import { createClient } from "./client";
import { isRustEditor, RustEditor } from "./util";
import { ServerStatusParams } from "./lsp_ext";
export type Workspace =
{
kind: 'Workspace Folder';
}
| {
kind: 'Detached Files';
files: vscode.TextDocument[];
};
kind: "Workspace Folder";
}
| {
kind: "Detached Files";
files: vscode.TextDocument[];
};
export class Ctx {
private constructor(
@ -22,16 +22,14 @@ export class Ctx {
private readonly extCtx: vscode.ExtensionContext,
readonly client: lc.LanguageClient,
readonly serverPath: string,
readonly statusBar: vscode.StatusBarItem,
) {
}
readonly statusBar: vscode.StatusBarItem
) {}
static async create(
config: Config,
extCtx: vscode.ExtensionContext,
serverPath: string,
workspace: Workspace,
workspace: Workspace
): Promise<Ctx> {
const client = await createClient(serverPath, workspace, config.serverExtraEnv);
@ -52,9 +50,7 @@ export class Ctx {
get activeRustEditor(): RustEditor | undefined {
const editor = vscode.window.activeTextEditor;
return editor && isRustEditor(editor)
? editor
: undefined;
return editor && isRustEditor(editor) ? editor : undefined;
}
get visibleRustEditors(): RustEditor[] {

View file

@ -1,14 +1,19 @@
import * as os from "os";
import * as vscode from 'vscode';
import * as path from 'path';
import * as ra from './lsp_ext';
import * as vscode from "vscode";
import * as path from "path";
import * as ra from "./lsp_ext";
import { Cargo, getRustcId, getSysroot } from './toolchain';
import { Cargo, getRustcId, getSysroot } from "./toolchain";
import { Ctx } from "./ctx";
import { prepareEnv } from "./run";
const debugOutput = vscode.window.createOutputChannel("Debug");
type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
type DebugConfigProvider = (
config: ra.Runnable,
executable: string,
env: Record<string, string>,
sourceFileMap?: Record<string, string>
) => vscode.DebugConfiguration;
export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
const scope = ctx.activeRustEditor?.document.uri;
@ -20,9 +25,13 @@ export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<
const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
const configurations = wsLaunchSection.get<any[]>("configurations") || [];
const index = configurations.findIndex(c => c.name === debugConfig.name);
const index = configurations.findIndex((c) => c.name === debugConfig.name);
if (index !== -1) {
const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
const answer = await vscode.window.showErrorMessage(
`Launch configuration '${debugConfig.name}' already exists!`,
"Cancel",
"Update"
);
if (answer === "Cancel") return;
configurations[index] = debugConfig;
@ -40,7 +49,7 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis
const wsLaunchSection = vscode.workspace.getConfiguration("launch");
const configurations = wsLaunchSection.get<any[]>("configurations") || [];
const index = configurations.findIndex(c => c.name === runnable.label);
const index = configurations.findIndex((c) => c.name === runnable.label);
if (-1 !== index) {
debugConfig = configurations[index];
message = " (from launch.json)";
@ -56,13 +65,16 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis
return vscode.debug.startDebugging(undefined, debugConfig);
}
async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<vscode.DebugConfiguration | undefined> {
async function getDebugConfiguration(
ctx: Ctx,
runnable: ra.Runnable
): Promise<vscode.DebugConfiguration | undefined> {
const editor = ctx.activeRustEditor;
if (!editor) return;
const knownEngines: Record<string, DebugConfigProvider> = {
"vadimcn.vscode-lldb": getLldbDebugConfig,
"ms-vscode.cpptools": getCppvsDebugConfig
"ms-vscode.cpptools": getCppvsDebugConfig,
};
const debugOptions = ctx.config.debug;
@ -77,8 +89,10 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
}
if (!debugEngine) {
await vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)`
+ ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`);
await vscode.window.showErrorMessage(
`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` +
` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`
);
return;
}
@ -91,15 +105,17 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
const workspaceFolders = vscode.workspace.workspaceFolders!;
const isMultiFolderWorkspace = workspaceFolders.length > 1;
const firstWorkspace = workspaceFolders[0];
const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ?
firstWorkspace :
workspaceFolders.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace;
const workspace =
!isMultiFolderWorkspace || !runnable.args.workspaceRoot
? firstWorkspace
: workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) ||
firstWorkspace;
const wsFolder = path.normalize(workspace.uri.fsPath);
const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : '';
const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : "";
function simplifyPath(p: string): string {
// see https://github.com/rust-analyzer/rust-analyzer/pull/5513#issuecomment-663458818 for why this is needed
return path.normalize(p).replace(wsFolder, '${workspaceFolder' + workspaceQualifier + '}');
return path.normalize(p).replace(wsFolder, "${workspaceFolder" + workspaceQualifier + "}");
}
const executable = await getDebugExecutable(runnable);
@ -114,7 +130,12 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
sourceFileMap[`/rustc/${commitHash}/`] = rustlib;
}
const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), env, sourceFileMap);
const debugConfig = knownEngines[debugEngine.id](
runnable,
simplifyPath(executable),
env,
sourceFileMap
);
if (debugConfig.type in debugOptions.engineSettings) {
const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
for (var key in settingsMap) {
@ -136,14 +157,19 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
}
async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput);
const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput);
const executable = await cargo.executableFromArgs(runnable.args.cargoArgs);
// if we are here, there were no compilation errors.
return executable;
}
function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
function getLldbDebugConfig(
runnable: ra.Runnable,
executable: string,
env: Record<string, string>,
sourceFileMap?: Record<string, string>
): vscode.DebugConfiguration {
return {
type: "lldb",
request: "launch",
@ -153,13 +179,18 @@ function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Reco
cwd: runnable.args.workspaceRoot,
sourceMap: sourceFileMap,
sourceLanguages: ["rust"],
env
env,
};
}
function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
function getCppvsDebugConfig(
runnable: ra.Runnable,
executable: string,
env: Record<string, string>,
sourceFileMap?: Record<string, string>
): vscode.DebugConfiguration {
return {
type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
type: os.platform() === "win32" ? "cppvsdbg" : "cppdbg",
request: "launch",
name: runnable.label,
program: executable,

View file

@ -7,7 +7,9 @@ import * as lc from "vscode-languageclient";
export interface AnalyzerStatusParams {
textDocument?: lc.TextDocumentIdentifier;
}
export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus");
export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>(
"rust-analyzer/analyzerStatus"
);
export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
export const shuffleCrateGraph = new lc.RequestType0<null, void>("rust-analyzer/shuffleCrateGraph");
@ -16,7 +18,9 @@ export interface ServerStatusParams {
quiescent: boolean;
message?: string;
}
export const serverStatus = new lc.NotificationType<ServerStatusParams>("experimental/serverStatus");
export const serverStatus = new lc.NotificationType<ServerStatusParams>(
"experimental/serverStatus"
);
export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
@ -31,23 +35,33 @@ export interface SyntaxTreeParams {
textDocument: lc.TextDocumentIdentifier;
range: lc.Range | null;
}
export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>("rust-analyzer/syntaxTree");
export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>(
"rust-analyzer/syntaxTree"
);
export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>("rust-analyzer/viewHir");
export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
"rust-analyzer/viewHir"
);
export const viewFileText = new lc.RequestType<lc.TextDocumentIdentifier, string, void>("rust-analyzer/viewFileText");
export const viewFileText = new lc.RequestType<lc.TextDocumentIdentifier, string, void>(
"rust-analyzer/viewFileText"
);
export interface ViewItemTreeParams {
textDocument: lc.TextDocumentIdentifier;
}
export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>("rust-analyzer/viewItemTree");
export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>(
"rust-analyzer/viewItemTree"
);
export interface ViewCrateGraphParams {
full: boolean;
}
export const viewCrateGraph = new lc.RequestType<ViewCrateGraphParams, string, void>("rust-analyzer/viewCrateGraph");
export const viewCrateGraph = new lc.RequestType<ViewCrateGraphParams, string, void>(
"rust-analyzer/viewCrateGraph"
);
export interface ExpandMacroParams {
textDocument: lc.TextDocumentIdentifier;
@ -57,23 +71,35 @@ export interface ExpandedMacro {
name: string;
expansion: string;
}
export const expandMacro = new lc.RequestType<ExpandMacroParams, ExpandedMacro | null, void>("rust-analyzer/expandMacro");
export const expandMacro = new lc.RequestType<ExpandMacroParams, ExpandedMacro | null, void>(
"rust-analyzer/expandMacro"
);
export interface MatchingBraceParams {
textDocument: lc.TextDocumentIdentifier;
positions: lc.Position[];
}
export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], void>("experimental/matchingBrace");
export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], void>(
"experimental/matchingBrace"
);
export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[] | null, void>("experimental/parentModule");
export const parentModule = new lc.RequestType<
lc.TextDocumentPositionParams,
lc.LocationLink[] | null,
void
>("experimental/parentModule");
export interface JoinLinesParams {
textDocument: lc.TextDocumentIdentifier;
ranges: lc.Range[];
}
export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], void>("experimental/joinLines");
export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], void>(
"experimental/joinLines"
);
export const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], void>("experimental/onEnter");
export const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], void>(
"experimental/onEnter"
);
export interface RunnablesParams {
textDocument: lc.TextDocumentIdentifier;
@ -93,13 +119,17 @@ export interface Runnable {
overrideCargo?: string;
};
}
export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("experimental/runnables");
export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>(
"experimental/runnables"
);
export interface TestInfo {
runnable: Runnable;
}
export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>("rust-analyzer/relatedTests");
export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>(
"rust-analyzer/relatedTests"
);
export interface SsrParams {
query: string;
@ -108,7 +138,7 @@ export interface SsrParams {
position: lc.Position;
selections: readonly lc.Range[];
}
export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr');
export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>("experimental/ssr");
export interface CommandLink extends lc.Command {
/**
@ -122,15 +152,21 @@ export interface CommandLinkGroup {
commands: CommandLink[];
}
export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>('experimental/externalDocs');
export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>(
"experimental/externalDocs"
);
export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>("experimental/openCargoToml");
export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>(
"experimental/openCargoToml"
);
export interface OpenCargoTomlParams {
textDocument: lc.TextDocumentIdentifier;
}
export const moveItem = new lc.RequestType<MoveItemParams, lc.TextEdit[], void>("experimental/moveItem");
export const moveItem = new lc.RequestType<MoveItemParams, lc.TextEdit[], void>(
"experimental/moveItem"
);
export interface MoveItemParams {
textDocument: lc.TextDocumentIdentifier;
@ -140,5 +176,5 @@ export interface MoveItemParams {
export const enum Direction {
Up = "Up",
Down = "Down"
Down = "Down",
}

View file

@ -1,15 +1,15 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient/node';
import * as vscode from "vscode";
import * as lc from "vscode-languageclient/node";
import * as os from "os";
import * as commands from './commands';
import { Ctx } from './ctx';
import { Config } from './config';
import { log, isValidExecutable, isRustDocument } from './util';
import { PersistentState } from './persistent_state';
import { activateTaskProvider } from './tasks';
import { setContextValue } from './util';
import { exec } from 'child_process';
import * as commands from "./commands";
import { Ctx } from "./ctx";
import { Config } from "./config";
import { log, isValidExecutable, isRustDocument } from "./util";
import { PersistentState } from "./persistent_state";
import { activateTaskProvider } from "./tasks";
import { setContextValue } from "./util";
import { exec } from "child_process";
let ctx: Ctx | undefined;
@ -19,10 +19,12 @@ export interface RustAnalyzerExtensionApi {
client: lc.LanguageClient;
}
export async function activate(context: vscode.ExtensionContext): Promise<RustAnalyzerExtensionApi> {
export async function activate(
context: vscode.ExtensionContext
): Promise<RustAnalyzerExtensionApi> {
// VS Code doesn't show a notification when an extension fails to activate
// so we do it ourselves.
return await tryActivate(context).catch(err => {
return await tryActivate(context).catch((err) => {
void vscode.window.showErrorMessage(`Cannot activate rust-analyzer: ${err.message}`);
throw err;
});
@ -31,7 +33,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<RustAn
async function tryActivate(context: vscode.ExtensionContext): Promise<RustAnalyzerExtensionApi> {
const config = new Config(context);
const state = new PersistentState(context.globalState);
const serverPath = await bootstrap(context, config, state).catch(err => {
const serverPath = await bootstrap(context, config, state).catch((err) => {
let message = "bootstrap error. ";
message += 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). ';
@ -42,9 +44,14 @@ async function tryActivate(context: vscode.ExtensionContext): Promise<RustAnalyz
});
if ((vscode.workspace.workspaceFolders || []).length === 0) {
const rustDocuments = vscode.workspace.textDocuments.filter(document => isRustDocument(document));
const rustDocuments = vscode.workspace.textDocuments.filter((document) =>
isRustDocument(document)
);
if (rustDocuments.length > 0) {
ctx = await Ctx.create(config, context, serverPath, { kind: 'Detached Files', files: rustDocuments });
ctx = await Ctx.create(config, context, serverPath, {
kind: "Detached Files",
files: rustDocuments,
});
} else {
throw new Error("no rust files are opened");
}
@ -63,13 +70,16 @@ async function tryActivate(context: vscode.ExtensionContext): Promise<RustAnalyz
ctx.pushCleanup(configureLanguage());
vscode.workspace.onDidChangeConfiguration(
_ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }).catch(log.error),
(_) =>
ctx?.client
?.sendNotification("workspace/didChangeConfiguration", { settings: "" })
.catch(log.error),
null,
ctx.subscriptions,
ctx.subscriptions
);
return {
client: ctx.client
client: ctx.client,
};
}
@ -88,9 +98,8 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
// "rust-analyzer is not available"
// ),
// )
const defaultOnEnter = vscode.commands.registerCommand(
'rust-analyzer.onEnter',
() => vscode.commands.executeCommand('default:type', { text: '\n' }),
const defaultOnEnter = vscode.commands.registerCommand("rust-analyzer.onEnter", () =>
vscode.commands.executeCommand("default:type", { text: "\n" })
);
context.subscriptions.push(defaultOnEnter);
@ -99,8 +108,8 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
// Commands which invokes manually via command palette, shortcut, etc.
// Reloading is inspired by @DanTup maneuver: https://github.com/microsoft/vscode/issues/45774#issuecomment-373423895
ctx.registerCommand('reload', _ => async () => {
void vscode.window.showInformationMessage('Reloading rust-analyzer...');
ctx.registerCommand("reload", (_) => async () => {
void vscode.window.showInformationMessage("Reloading rust-analyzer...");
await deactivate();
while (context.subscriptions.length > 0) {
try {
@ -112,45 +121,45 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
await activate(context).catch(log.error);
});
ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
ctx.registerCommand('memoryUsage', commands.memoryUsage);
ctx.registerCommand('shuffleCrateGraph', commands.shuffleCrateGraph);
ctx.registerCommand('reloadWorkspace', commands.reloadWorkspace);
ctx.registerCommand('matchingBrace', commands.matchingBrace);
ctx.registerCommand('joinLines', commands.joinLines);
ctx.registerCommand('parentModule', commands.parentModule);
ctx.registerCommand('syntaxTree', commands.syntaxTree);
ctx.registerCommand('viewHir', commands.viewHir);
ctx.registerCommand('viewFileText', commands.viewFileText);
ctx.registerCommand('viewItemTree', commands.viewItemTree);
ctx.registerCommand('viewCrateGraph', commands.viewCrateGraph);
ctx.registerCommand('viewFullCrateGraph', commands.viewFullCrateGraph);
ctx.registerCommand('expandMacro', commands.expandMacro);
ctx.registerCommand('run', commands.run);
ctx.registerCommand('copyRunCommandLine', commands.copyRunCommandLine);
ctx.registerCommand('debug', commands.debug);
ctx.registerCommand('newDebugConfig', commands.newDebugConfig);
ctx.registerCommand('openDocs', commands.openDocs);
ctx.registerCommand('openCargoToml', commands.openCargoToml);
ctx.registerCommand('peekTests', commands.peekTests);
ctx.registerCommand('moveItemUp', commands.moveItemUp);
ctx.registerCommand('moveItemDown', commands.moveItemDown);
ctx.registerCommand("analyzerStatus", commands.analyzerStatus);
ctx.registerCommand("memoryUsage", commands.memoryUsage);
ctx.registerCommand("shuffleCrateGraph", commands.shuffleCrateGraph);
ctx.registerCommand("reloadWorkspace", commands.reloadWorkspace);
ctx.registerCommand("matchingBrace", commands.matchingBrace);
ctx.registerCommand("joinLines", commands.joinLines);
ctx.registerCommand("parentModule", commands.parentModule);
ctx.registerCommand("syntaxTree", commands.syntaxTree);
ctx.registerCommand("viewHir", commands.viewHir);
ctx.registerCommand("viewFileText", commands.viewFileText);
ctx.registerCommand("viewItemTree", commands.viewItemTree);
ctx.registerCommand("viewCrateGraph", commands.viewCrateGraph);
ctx.registerCommand("viewFullCrateGraph", commands.viewFullCrateGraph);
ctx.registerCommand("expandMacro", commands.expandMacro);
ctx.registerCommand("run", commands.run);
ctx.registerCommand("copyRunCommandLine", commands.copyRunCommandLine);
ctx.registerCommand("debug", commands.debug);
ctx.registerCommand("newDebugConfig", commands.newDebugConfig);
ctx.registerCommand("openDocs", commands.openDocs);
ctx.registerCommand("openCargoToml", commands.openCargoToml);
ctx.registerCommand("peekTests", commands.peekTests);
ctx.registerCommand("moveItemUp", commands.moveItemUp);
ctx.registerCommand("moveItemDown", commands.moveItemDown);
defaultOnEnter.dispose();
ctx.registerCommand('onEnter', commands.onEnter);
ctx.registerCommand("onEnter", commands.onEnter);
ctx.registerCommand('ssr', commands.ssr);
ctx.registerCommand('serverVersion', commands.serverVersion);
ctx.registerCommand('toggleInlayHints', commands.toggleInlayHints);
ctx.registerCommand("ssr", commands.ssr);
ctx.registerCommand("serverVersion", commands.serverVersion);
ctx.registerCommand("toggleInlayHints", commands.toggleInlayHints);
// Internal commands which are invoked by the server.
ctx.registerCommand('runSingle', commands.runSingle);
ctx.registerCommand('debugSingle', commands.debugSingle);
ctx.registerCommand('showReferences', commands.showReferences);
ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand);
ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction);
ctx.registerCommand('applyActionGroup', commands.applyActionGroup);
ctx.registerCommand('gotoLocation', commands.gotoLocation);
ctx.registerCommand("runSingle", commands.runSingle);
ctx.registerCommand("debugSingle", commands.debugSingle);
ctx.registerCommand("showReferences", commands.showReferences);
ctx.registerCommand("applySnippetWorkspaceEdit", commands.applySnippetWorkspaceEditCommand);
ctx.registerCommand("resolveCodeAction", commands.resolveCodeAction);
ctx.registerCommand("applyActionGroup", commands.applyActionGroup);
ctx.registerCommand("gotoLocation", commands.gotoLocation);
}
export async function deactivate() {
@ -159,12 +168,16 @@ export async function deactivate() {
ctx = undefined;
}
async function bootstrap(context: vscode.ExtensionContext, config: Config, state: PersistentState): Promise<string> {
async function bootstrap(
context: vscode.ExtensionContext,
config: Config,
state: PersistentState
): Promise<string> {
const path = await getServer(context, config, state);
if (!path) {
throw new Error(
"Rust Analyzer Language Server is not available. " +
"Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)."
"Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)."
);
}
@ -186,7 +199,7 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: "Patching rust-analyzer for NixOS"
title: "Patching rust-analyzer for NixOS",
},
async (progress, _) => {
const expression = `
@ -207,14 +220,16 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
try {
progress.report({ message: "Patching executable", increment: 20 });
await new Promise((resolve, reject) => {
const handle = exec(`nix-build -E - --argstr srcStr '${origFile.fsPath}' -o '${dest.fsPath}'`,
const handle = exec(
`nix-build -E - --argstr srcStr '${origFile.fsPath}' -o '${dest.fsPath}'`,
(err, stdout, stderr) => {
if (err != null) {
reject(Error(stderr));
} else {
resolve(stdout);
}
});
}
);
handle.stdin?.write(expression);
handle.stdin?.end();
});
@ -225,25 +240,35 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
);
}
async function getServer(context: vscode.ExtensionContext, config: Config, state: PersistentState): Promise<string | undefined> {
async function getServer(
context: vscode.ExtensionContext,
config: Config,
state: PersistentState
): Promise<string | undefined> {
const explicitPath = serverPath(config);
if (explicitPath) {
if (explicitPath.startsWith("~/")) {
return os.homedir() + explicitPath.slice("~".length);
}
return explicitPath;
};
}
if (config.package.releaseTag === null) return "rust-analyzer";
const ext = process.platform === "win32" ? ".exe" : "";
const bundled = vscode.Uri.joinPath(context.extensionUri, "server", `rust-analyzer${ext}`);
const bundledExists = await vscode.workspace.fs.stat(bundled).then(() => true, () => false);
const bundledExists = await vscode.workspace.fs.stat(bundled).then(
() => true,
() => false
);
if (bundledExists) {
let server = bundled;
if (await isNixOs()) {
await vscode.workspace.fs.createDirectory(config.globalStorageUri).then();
const dest = vscode.Uri.joinPath(config.globalStorageUri, `rust-analyzer${ext}`);
let exists = await vscode.workspace.fs.stat(dest).then(() => true, () => false);
let exists = await vscode.workspace.fs.stat(dest).then(
() => true,
() => false
);
if (exists && config.package.version !== state.serverVersion) {
await vscode.workspace.fs.delete(dest);
exists = false;
@ -261,11 +286,11 @@ async function getServer(context: vscode.ExtensionContext, config: Config, state
await state.updateServerVersion(undefined);
await vscode.window.showErrorMessage(
"Unfortunately we don't ship binaries for your platform yet. " +
"You need to manually clone the rust-analyzer repository and " +
"run `cargo xtask install --server` to build the language server from sources. " +
"If you feel that your platform should be supported, please create an issue " +
"about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " +
"will consider it."
"You need to manually clone the rust-analyzer repository and " +
"run `cargo xtask install --server` to build the language server from sources. " +
"If you feel that your platform should be supported, please create an issue " +
"about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " +
"will consider it."
);
return undefined;
}
@ -276,8 +301,10 @@ function serverPath(config: Config): string | null {
async function isNixOs(): Promise<boolean> {
try {
const contents = (await vscode.workspace.fs.readFile(vscode.Uri.file("/etc/os-release"))).toString();
const idString = contents.split('\n').find((a) => a.startsWith("ID=")) || "ID=linux";
const contents = (
await vscode.workspace.fs.readFile(vscode.Uri.file("/etc/os-release"))
).toString();
const idString = contents.split("\n").find((a) => a.startsWith("ID=")) || "ID=linux";
return idString.indexOf("nixos") !== -1;
} catch {
return false;
@ -286,11 +313,14 @@ async function isNixOs(): Promise<boolean> {
function warnAboutExtensionConflicts() {
if (vscode.extensions.getExtension("rust-lang.rust")) {
vscode.window.showWarningMessage(
`You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` +
"plugins enabled. These are known to conflict and cause various functions of " +
"both plugins to not work correctly. You should disable one of them.", "Got it")
.then(() => { }, console.error);
vscode.window
.showWarningMessage(
`You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` +
"plugins enabled. These are known to conflict and cause various functions of " +
"both plugins to not work correctly. You should disable one of them.",
"Got it"
)
.then(() => {}, console.error);
}
}
@ -302,38 +332,38 @@ function warnAboutExtensionConflicts() {
*/
function configureLanguage(): vscode.Disposable {
const indentAction = vscode.IndentAction.None;
return vscode.languages.setLanguageConfiguration('rust', {
return vscode.languages.setLanguageConfiguration("rust", {
onEnterRules: [
{
// Doc single-line comment
// e.g. ///|
beforeText: /^\s*\/{3}.*$/,
action: { indentAction, appendText: '/// ' },
action: { indentAction, appendText: "/// " },
},
{
// Parent doc single-line comment
// e.g. //!|
beforeText: /^\s*\/{2}\!.*$/,
action: { indentAction, appendText: '//! ' },
action: { indentAction, appendText: "//! " },
},
{
// Begins an auto-closed multi-line comment (standard or parent doc)
// e.g. /** | */ or /*! | */
beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/,
afterText: /^\s*\*\/$/,
action: { indentAction: vscode.IndentAction.IndentOutdent, appendText: ' * ' },
action: { indentAction: vscode.IndentAction.IndentOutdent, appendText: " * " },
},
{
// Begins a multi-line comment (standard or parent doc)
// e.g. /** ...| or /*! ...|
beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/,
action: { indentAction, appendText: ' * ' },
action: { indentAction, appendText: " * " },
},
{
// Continues a multi-line comment
// e.g. * ...|
beforeText: /^(\ \ )*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
action: { indentAction, appendText: '* ' },
action: { indentAction, appendText: "* " },
},
{
// Dedents after closing a multi-line comment

View file

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import { log } from './util';
import * as vscode from "vscode";
import { log } from "./util";
export class PersistentState {
constructor(private readonly globalState: vscode.Memento) {

View file

@ -1,15 +1,22 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from './lsp_ext';
import * as tasks from './tasks';
import * as vscode from "vscode";
import * as lc from "vscode-languageclient";
import * as ra from "./lsp_ext";
import * as tasks from "./tasks";
import { Ctx } from './ctx';
import { makeDebugConfig } from './debug';
import { Config, RunnableEnvCfg } from './config';
import { Ctx } from "./ctx";
import { makeDebugConfig } from "./debug";
import { Config, RunnableEnvCfg } from "./config";
const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
const quickPickButtons = [
{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." },
];
export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise<RunnableQuickPick | undefined> {
export async function selectRunnable(
ctx: Ctx,
prevRunnable?: RunnableQuickPick,
debuggeeOnly = false,
showButtons: boolean = true
): Promise<RunnableQuickPick | undefined> {
const editor = ctx.activeRustEditor;
const client = ctx.client;
if (!editor || !client) return;
@ -20,23 +27,18 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
const runnables = await client.sendRequest(ra.runnables, {
textDocument,
position: client.code2ProtocolConverter.asPosition(
editor.selection.active,
),
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
});
const items: RunnableQuickPick[] = [];
if (prevRunnable) {
items.push(prevRunnable);
}
for (const r of runnables) {
if (
prevRunnable &&
JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
) {
if (prevRunnable && JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)) {
continue;
}
if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) {
if (debuggeeOnly && (r.label.startsWith("doctest") || r.label.startsWith("cargo"))) {
continue;
}
items.push(new RunnableQuickPick(r));
@ -53,7 +55,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
const disposables: vscode.Disposable[] = [];
const close = (result?: RunnableQuickPick) => {
resolve(result);
disposables.forEach(d => d.dispose());
disposables.forEach((d) => d.dispose());
};
const quickPick = vscode.window.createQuickPick<RunnableQuickPick>();
@ -71,7 +73,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
}),
quickPick.onDidChangeActive((active) => {
if (showButtons && active.length > 0) {
if (active[0].label.startsWith('cargo')) {
if (active[0].label.startsWith("cargo")) {
// save button makes no sense for `cargo test` or `cargo check`
quickPick.buttons = [];
} else if (quickPick.buttons.length === 0) {
@ -96,8 +98,11 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
}
}
export function prepareEnv(runnable: ra.Runnable, runnableEnvCfg: RunnableEnvCfg): Record<string, string> {
const env: Record<string, string> = { "RUST_BACKTRACE": "short" };
export function prepareEnv(
runnable: ra.Runnable,
runnableEnvCfg: RunnableEnvCfg
): Record<string, string> {
const env: Record<string, string> = { RUST_BACKTRACE: "short" };
if (runnable.args.expectTest) {
env["UPDATE_EXPECT"] = "1";
@ -141,7 +146,14 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true);
const cargoTask = await tasks.buildCargoTask(
target,
definition,
runnable.label,
args,
config.cargoRunner,
true
);
cargoTask.presentationOptions.clear = true;
// Sadly, this doesn't prevent focus stealing if the terminal is currently
@ -157,7 +169,7 @@ export function createArgs(runnable: ra.Runnable): string[] {
args.push(...runnable.args.cargoExtraArgs); // Append user-specified cargo options.
}
if (runnable.args.executableArgs.length > 0) {
args.push('--', ...runnable.args.executableArgs);
args.push("--", ...runnable.args.executableArgs);
}
return args;
}

View file

@ -1,6 +1,6 @@
import * as vscode from 'vscode';
import * as vscode from "vscode";
import { assert } from './util';
import { assert } from "./util";
export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
if (edit.entries().length === 1) {
@ -11,12 +11,17 @@ export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
}
for (const [uri, edits] of edit.entries()) {
const editor = await editorFromUri(uri);
if (editor) await editor.edit((builder) => {
for (const indel of edits) {
assert(!parseSnippet(indel.newText), `bad ws edit: snippet received with multiple edits: ${JSON.stringify(edit)}`);
builder.replace(indel.range, indel.newText);
}
});
if (editor) {
await editor.edit((builder) => {
for (const indel of edits) {
assert(
!parseSnippet(indel.newText),
`bad ws edit: snippet received with multiple edits: ${JSON.stringify(edit)}`
);
builder.replace(indel.range, indel.newText);
}
});
}
}
}
@ -25,7 +30,9 @@ async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undef
// `vscode.window.visibleTextEditors` only contains editors whose contents are being displayed
await vscode.window.showTextDocument(uri, {});
}
return vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
return vscode.window.visibleTextEditors.find(
(it) => it.document.uri.toString() === uri.toString()
);
}
export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) {
@ -37,22 +44,26 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs
if (parsed) {
const [newText, [placeholderStart, placeholderLength]] = parsed;
const prefix = newText.substr(0, placeholderStart);
const lastNewline = prefix.lastIndexOf('\n');
const lastNewline = prefix.lastIndexOf("\n");
const startLine = indel.range.start.line + lineDelta + countLines(prefix);
const startColumn = lastNewline === -1 ?
indel.range.start.character + placeholderStart
: prefix.length - lastNewline - 1;
const startColumn =
lastNewline === -1
? indel.range.start.character + placeholderStart
: prefix.length - lastNewline - 1;
const endColumn = startColumn + placeholderLength;
selections.push(new vscode.Selection(
new vscode.Position(startLine, startColumn),
new vscode.Position(startLine, endColumn),
));
selections.push(
new vscode.Selection(
new vscode.Position(startLine, startColumn),
new vscode.Position(startLine, endColumn)
)
);
builder.replace(indel.range, newText);
} else {
builder.replace(indel.range, indel.newText);
}
lineDelta += countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
lineDelta +=
countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
}
});
if (selections.length > 0) editor.selections = selections;
@ -65,8 +76,7 @@ function parseSnippet(snip: string): [string, [number, number]] | undefined {
const m = snip.match(/\$(0|\{0:([^}]*)\})/);
if (!m) return undefined;
const placeholder = m[2] ?? "";
if (m.index == null)
return undefined;
if (m.index == null) return undefined;
const range: [number, number] = [m.index, placeholder.length];
const insert = snip.replace(m[0], placeholder);
return [insert, range];

View file

@ -1,12 +1,12 @@
import * as vscode from 'vscode';
import * as vscode from "vscode";
import * as toolchain from "./toolchain";
import { Config } from './config';
import { log } from './util';
import { Config } from "./config";
import { log } from "./util";
// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
// our configuration should be compatible with it so use the same key.
export const TASK_TYPE = 'cargo';
export const TASK_SOURCE = 'rust';
export const TASK_TYPE = "cargo";
export const TASK_SOURCE = "rust";
export interface CargoTaskDefinition extends vscode.TaskDefinition {
command?: string;
@ -30,17 +30,23 @@ class CargoTaskProvider implements vscode.TaskProvider {
// tasks.json - only tweaked.
const defs = [
{ command: 'build', group: vscode.TaskGroup.Build },
{ command: 'check', group: vscode.TaskGroup.Build },
{ command: 'test', group: vscode.TaskGroup.Test },
{ command: 'clean', group: vscode.TaskGroup.Clean },
{ command: 'run', group: undefined },
{ command: "build", group: vscode.TaskGroup.Build },
{ command: "check", group: vscode.TaskGroup.Build },
{ command: "test", group: vscode.TaskGroup.Test },
{ command: "clean", group: vscode.TaskGroup.Clean },
{ command: "run", group: undefined },
];
const tasks: vscode.Task[] = [];
for (const workspaceTarget of vscode.workspace.workspaceFolders || []) {
for (const def of defs) {
const vscodeTask = await buildCargoTask(workspaceTarget, { type: TASK_TYPE, command: def.command }, `cargo ${def.command}`, [def.command], this.config.cargoRunner);
const vscodeTask = await buildCargoTask(
workspaceTarget,
{ type: TASK_TYPE, command: def.command },
`cargo ${def.command}`,
[def.command],
this.config.cargoRunner
);
vscodeTask.group = def.group;
tasks.push(vscodeTask);
}
@ -58,7 +64,13 @@ class CargoTaskProvider implements vscode.TaskProvider {
if (definition.type === TASK_TYPE && definition.command) {
const args = [definition.command].concat(definition.args ?? []);
return await buildCargoTask(task.scope, definition, task.name, args, this.config.cargoRunner);
return await buildCargoTask(
task.scope,
definition,
task.name,
args,
this.config.cargoRunner
);
}
return undefined;
@ -73,7 +85,6 @@ export async function buildCargoTask(
customRunner?: string,
throwOnError: boolean = false
): Promise<vscode.Task> {
let exec: vscode.ProcessExecution | vscode.ShellExecution | undefined = undefined;
if (customRunner) {
@ -90,7 +101,6 @@ export async function buildCargoTask(
}
}
// fallback to default processing
} catch (e) {
if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`;
// fallback to default processing
@ -117,7 +127,7 @@ export async function buildCargoTask(
name,
TASK_SOURCE,
exec,
['$rustc']
["$rustc"]
);
}

View file

@ -1,9 +1,9 @@
import * as cp from 'child_process';
import * as os from 'os';
import * as path from 'path';
import * as readline from 'readline';
import * as vscode from 'vscode';
import { execute, log, memoizeAsync } from './util';
import * as cp from "child_process";
import * as os from "os";
import * as path from "path";
import * as readline from "readline";
import * as vscode from "vscode";
import { execute, log, memoizeAsync } from "./util";
interface CompilationArtifact {
fileName: string;
@ -18,7 +18,7 @@ export interface ArtifactSpec {
}
export class Cargo {
constructor(readonly rootFolder: string, readonly output: vscode.OutputChannel) { }
constructor(readonly rootFolder: string, readonly output: vscode.OutputChannel) {}
// Made public for testing purposes
static artifactSpec(args: readonly string[]): ArtifactSpec {
@ -27,7 +27,9 @@ export class Cargo {
// arguments for a runnable from the quick pick should be updated.
// see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens
switch (cargoArgs[0]) {
case "run": cargoArgs[0] = "build"; break;
case "run":
cargoArgs[0] = "build";
break;
case "test": {
if (!cargoArgs.includes("--no-run")) {
cargoArgs.push("--no-run");
@ -40,7 +42,7 @@ export class Cargo {
if (cargoArgs[0] === "test") {
// for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests
// produce 2 artifacts: {"kind": "bin"} and {"kind": "test"}
result.filter = (artifacts) => artifacts.filter(it => it.isTest);
result.filter = (artifacts) => artifacts.filter((it) => it.isTest);
}
return result;
@ -50,24 +52,25 @@ export class Cargo {
const artifacts: CompilationArtifact[] = [];
try {
await this.runCargo(spec.cargoArgs,
message => {
if (message.reason === 'compiler-artifact' && message.executable) {
const isBinary = message.target.crate_types.includes('bin');
const isBuildScript = message.target.kind.includes('custom-build');
await this.runCargo(
spec.cargoArgs,
(message) => {
if (message.reason === "compiler-artifact" && message.executable) {
const isBinary = message.target.crate_types.includes("bin");
const isBuildScript = message.target.kind.includes("custom-build");
if ((isBinary && !isBuildScript) || message.profile.test) {
artifacts.push({
fileName: message.executable,
name: message.target.name,
kind: message.target.kind[0],
isTest: message.profile.test
isTest: message.profile.test,
});
}
} else if (message.reason === 'compiler-message') {
} else if (message.reason === "compiler-message") {
this.output.append(message.message.rendered);
}
},
stderr => this.output.append(stderr),
(stderr) => this.output.append(stderr)
);
} catch (err) {
this.output.show(true);
@ -81,9 +84,9 @@ export class Cargo {
const artifacts = await this.getArtifacts(Cargo.artifactSpec(args));
if (artifacts.length === 0) {
throw new Error('No compilation artifacts');
throw new Error("No compilation artifacts");
} else if (artifacts.length > 1) {
throw new Error('Multiple compilation artifacts are not supported.');
throw new Error("Multiple compilation artifacts are not supported.");
}
return artifacts[0].fileName;
@ -97,25 +100,23 @@ export class Cargo {
const path = await cargoPath();
return await new Promise((resolve, reject) => {
const cargo = cp.spawn(path, cargoArgs, {
stdio: ['ignore', 'pipe', 'pipe'],
cwd: this.rootFolder
stdio: ["ignore", "pipe", "pipe"],
cwd: this.rootFolder,
});
cargo.on('error', err => reject(new Error(`could not launch cargo: ${err}`)));
cargo.on("error", (err) => reject(new Error(`could not launch cargo: ${err}`)));
cargo.stderr.on('data', chunk => onStderrString(chunk.toString()));
cargo.stderr.on("data", (chunk) => onStderrString(chunk.toString()));
const rl = readline.createInterface({ input: cargo.stdout });
rl.on('line', line => {
rl.on("line", (line) => {
const message = JSON.parse(line);
onStdoutJson(message);
});
cargo.on('exit', (exitCode, _) => {
if (exitCode === 0)
resolve(exitCode);
else
reject(new Error(`exit code: ${exitCode}.`));
cargo.on("exit", (exitCode, _) => {
if (exitCode === 0) resolve(exitCode);
else reject(new Error(`exit code: ${exitCode}.`));
});
});
}
@ -158,7 +159,12 @@ export const getPathForExecutable = memoizeAsync(
try {
// hmm, `os.homedir()` seems to be infallible
// it is not mentioned in docs and cannot be infered by the type signature...
const standardPath = vscode.Uri.joinPath(vscode.Uri.file(os.homedir()), ".cargo", "bin", executableName);
const standardPath = vscode.Uri.joinPath(
vscode.Uri.file(os.homedir()),
".cargo",
"bin",
executableName
);
if (await isFileAtUri(standardPath)) return standardPath.fsPath;
} catch (err) {
@ -169,13 +175,11 @@ export const getPathForExecutable = memoizeAsync(
);
async function lookupInPath(exec: string): Promise<boolean> {
const paths = process.env.PATH ?? "";;
const paths = process.env.PATH ?? "";
const candidates = paths.split(path.delimiter).flatMap(dirInPath => {
const candidates = paths.split(path.delimiter).flatMap((dirInPath) => {
const candidate = path.join(dirInPath, exec);
return os.type() === "Windows_NT"
? [candidate, `${candidate}.exe`]
: [candidate];
return os.type() === "Windows_NT" ? [candidate, `${candidate}.exe`] : [candidate];
});
for await (const isFile of candidates.map(isFileAtPath)) {

View file

@ -13,7 +13,7 @@ export function assert(condition: boolean, explanation: string): asserts conditi
}
}
export const log = new class {
export const log = new (class {
private enabled = true;
private readonly output = vscode.window.createOutputChannel("Rust Analyzer Client");
@ -55,21 +55,20 @@ export const log = new class {
depth: 6, // heuristic
});
}
};
})();
export async function sendRequestWithRetry<TParam, TRet>(
client: lc.LanguageClient,
reqType: lc.RequestType<TParam, TRet, unknown>,
param: TParam,
token?: vscode.CancellationToken,
token?: vscode.CancellationToken
): Promise<TRet> {
// The sequence is `10 * (2 ** (2 * n))` where n is 1, 2, 3...
for (const delay of [40, 160, 640, 2560, 10240, null]) {
try {
return await (token
? client.sendRequest(reqType, param, token)
: client.sendRequest(reqType, param)
);
: client.sendRequest(reqType, param));
} catch (error) {
if (delay === null) {
log.warn("LSP request timed out", { method: reqType.method, param, error });
@ -86,11 +85,11 @@ export async function sendRequestWithRetry<TParam, TRet>(
await sleep(delay);
}
}
throw 'unreachable';
throw "unreachable";
}
export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
return new Promise((resolve) => setTimeout(resolve, ms));
}
export type RustDocument = vscode.TextDocument & { languageId: "rust" };
@ -101,12 +100,12 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD
// by allowing only `file` schemes
// unfortunately extensions that use diff views not always set this
// to something different than 'file' (see ongoing bug: #4608)
return document.languageId === 'rust' && document.uri.scheme === 'file';
return document.languageId === "rust" && document.uri.scheme === "file";
}
export function isCargoTomlDocument(document: vscode.TextDocument): document is RustDocument {
// ideally `document.languageId` should be 'toml' but user maybe not have toml extension installed
return document.uri.scheme === 'file' && document.fileName.endsWith('Cargo.toml');
return document.uri.scheme === "file" && document.fileName.endsWith("Cargo.toml");
}
export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
@ -116,9 +115,9 @@ export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
export function isValidExecutable(path: string): boolean {
log.debug("Checking availability of a binary at", path);
const res = spawnSync(path, ["--version"], { encoding: 'utf8' });
const res = spawnSync(path, ["--version"], { encoding: "utf8" });
const printOutput = res.error && (res.error as any).code !== 'ENOENT' ? log.warn : log.debug;
const printOutput = res.error && (res.error as any).code !== "ENOENT" ? log.warn : log.debug;
printOutput(path, "--version:", res);
return res.status === 0;
@ -126,17 +125,19 @@ export function isValidExecutable(path: string): boolean {
/** Sets ['when'](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts) clause contexts */
export function setContextValue(key: string, value: any): Thenable<void> {
return vscode.commands.executeCommand('setContext', key, value);
return vscode.commands.executeCommand("setContext", key, value);
}
/**
* Returns a higher-order function that caches the results of invoking the
* underlying async function.
*/
export function memoizeAsync<Ret, TThis, Param extends string>(func: (this: TThis, arg: Param) => Promise<Ret>) {
export function memoizeAsync<Ret, TThis, Param extends string>(
func: (this: TThis, arg: Param) => Promise<Ret>
) {
const cache = new Map<string, Ret>();
return async function(this: TThis, arg: Param) {
return async function (this: TThis, arg: Param) {
const cached = cache.get(arg);
if (cached) return cached;

View file

@ -1,43 +1,43 @@
import * as path from 'path';
import * as fs from 'fs';
import * as path from "path";
import * as fs from "fs";
import { runTests } from '@vscode/test-electron';
import { runTests } from "@vscode/test-electron";
async function main() {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
const extensionDevelopmentPath = path.resolve(__dirname, "../../");
// Minimum supported version.
const jsonData = fs.readFileSync(path.join(extensionDevelopmentPath, 'package.json'));
const jsonData = fs.readFileSync(path.join(extensionDevelopmentPath, "package.json"));
const json = JSON.parse(jsonData.toString());
let minimalVersion: string = json.engines.vscode;
if (minimalVersion.startsWith('^')) minimalVersion = minimalVersion.slice(1);
if (minimalVersion.startsWith("^")) minimalVersion = minimalVersion.slice(1);
const launchArgs = ["--disable-extensions", extensionDevelopmentPath];
// All test suites (either unit tests or integration tests) should be in subfolders.
const extensionTestsPath = path.resolve(__dirname, './unit/index');
const extensionTestsPath = path.resolve(__dirname, "./unit/index");
// Run tests using the minimal supported version.
await runTests({
version: minimalVersion,
launchArgs,
extensionDevelopmentPath,
extensionTestsPath
extensionTestsPath,
});
// and the latest one
await runTests({
version: 'stable',
version: "stable",
launchArgs,
extensionDevelopmentPath,
extensionTestsPath
extensionTestsPath,
});
}
main().catch(err => {
main().catch((err) => {
// eslint-disable-next-line no-console
console.error('Failed to run tests', err);
console.error("Failed to run tests", err);
process.exit(1);
});

View file

@ -1,5 +1,5 @@
import { readdir } from 'fs/promises';
import * as path from 'path';
import { readdir } from "fs/promises";
import * as path from "path";
class Test {
readonly name: string;
@ -59,7 +59,9 @@ export class Context {
export async function run(): Promise<void> {
const context = new Context();
const testFiles = (await readdir(path.resolve(__dirname))).filter(name => name.endsWith('.test.js'));
const testFiles = (await readdir(path.resolve(__dirname))).filter((name) =>
name.endsWith(".test.js")
);
for (const testFile of testFiles) {
try {
const testModule = require(path.resolve(__dirname, testFile));

View file

@ -1,51 +1,98 @@
import * as assert from 'assert';
import { Cargo } from '../../src/toolchain';
import { Context } from '.';
import * as assert from "assert";
import { Cargo } from "../../src/toolchain";
import { Context } from ".";
export async function getTests(ctx: Context) {
await ctx.suite('Launch configuration/Lens', suite => {
suite.addTest('A binary', async () => {
const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]);
await ctx.suite("Launch configuration/Lens", (suite) => {
suite.addTest("A binary", async () => {
const args = Cargo.artifactSpec([
"build",
"--package",
"pkg_name",
"--bin",
"pkg_name",
]);
assert.deepStrictEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]);
assert.deepStrictEqual(args.cargoArgs, [
"build",
"--package",
"pkg_name",
"--bin",
"pkg_name",
"--message-format=json",
]);
assert.deepStrictEqual(args.filter, undefined);
});
suite.addTest('One of Multiple Binaries', async () => {
suite.addTest("One of Multiple Binaries", async () => {
const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]);
assert.deepStrictEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin1", "--message-format=json"]);
assert.deepStrictEqual(args.cargoArgs, [
"build",
"--package",
"pkg_name",
"--bin",
"bin1",
"--message-format=json",
]);
assert.deepStrictEqual(args.filter, undefined);
});
suite.addTest('A test', async () => {
suite.addTest("A test", async () => {
const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]);
assert.deepStrictEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--no-run", "--message-format=json"]);
assert.deepStrictEqual(args.cargoArgs, [
"test",
"--package",
"pkg_name",
"--lib",
"--no-run",
"--message-format=json",
]);
assert.notDeepStrictEqual(args.filter, undefined);
});
});
await ctx.suite('Launch configuration/QuickPick', suite => {
suite.addTest('A binary', async () => {
await ctx.suite("Launch configuration/QuickPick", (suite) => {
suite.addTest("A binary", async () => {
const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]);
assert.deepStrictEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]);
assert.deepStrictEqual(args.cargoArgs, [
"build",
"--package",
"pkg_name",
"--bin",
"pkg_name",
"--message-format=json",
]);
assert.deepStrictEqual(args.filter, undefined);
});
suite.addTest('One of Multiple Binaries', async () => {
suite.addTest("One of Multiple Binaries", async () => {
const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]);
assert.deepStrictEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin2", "--message-format=json"]);
assert.deepStrictEqual(args.cargoArgs, [
"build",
"--package",
"pkg_name",
"--bin",
"bin2",
"--message-format=json",
]);
assert.deepStrictEqual(args.filter, undefined);
});
suite.addTest('A test', async () => {
suite.addTest("A test", async () => {
const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]);
assert.deepStrictEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--message-format=json", "--no-run"]);
assert.deepStrictEqual(args.cargoArgs, [
"test",
"--package",
"pkg_name",
"--lib",
"--message-format=json",
"--no-run",
]);
assert.notDeepStrictEqual(args.filter, undefined);
});
});

View file

@ -1,8 +1,8 @@
import * as assert from 'assert';
import { prepareEnv } from '../../src/run';
import { RunnableEnvCfg } from '../../src/config';
import { Context } from '.';
import * as ra from '../../src/lsp_ext';
import * as assert from "assert";
import { prepareEnv } from "../../src/run";
import { RunnableEnvCfg } from "../../src/config";
import { Context } from ".";
import * as ra from "../../src/lsp_ext";
function makeRunnable(label: string): ra.Runnable {
return {
@ -11,8 +11,8 @@ function makeRunnable(label: string): ra.Runnable {
args: {
cargoArgs: [],
executableArgs: [],
cargoExtraArgs: []
}
cargoExtraArgs: [],
},
};
}
@ -22,20 +22,20 @@ function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<st
}
export async function getTests(ctx: Context) {
await ctx.suite('Runnable env', suite => {
suite.addTest('Global config works', async () => {
const binEnv = fakePrepareEnv("run project_name", { "GLOBAL": "g" });
await ctx.suite("Runnable env", (suite) => {
suite.addTest("Global config works", async () => {
const binEnv = fakePrepareEnv("run project_name", { GLOBAL: "g" });
assert.strictEqual(binEnv["GLOBAL"], "g");
const testEnv = fakePrepareEnv("test some::mod::test_name", { "GLOBAL": "g" });
const testEnv = fakePrepareEnv("test some::mod::test_name", { GLOBAL: "g" });
assert.strictEqual(testEnv["GLOBAL"], "g");
});
suite.addTest('null mask works', async () => {
suite.addTest("null mask works", async () => {
const config = [
{
env: { DATA: "data" }
}
env: { DATA: "data" },
},
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.strictEqual(binEnv["DATA"], "data");
@ -44,14 +44,14 @@ export async function getTests(ctx: Context) {
assert.strictEqual(testEnv["DATA"], "data");
});
suite.addTest('order works', async () => {
suite.addTest("order works", async () => {
const config = [
{
env: { DATA: "data" }
env: { DATA: "data" },
},
{
env: { DATA: "newdata" }
}
env: { DATA: "newdata" },
},
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.strictEqual(binEnv["DATA"], "newdata");
@ -60,19 +60,19 @@ export async function getTests(ctx: Context) {
assert.strictEqual(testEnv["DATA"], "newdata");
});
suite.addTest('mask works', async () => {
suite.addTest("mask works", async () => {
const config = [
{
env: { DATA: "data" }
env: { DATA: "data" },
},
{
mask: "^run",
env: { DATA: "rundata" }
env: { DATA: "rundata" },
},
{
mask: "special_test$",
env: { DATA: "special_test" }
}
env: { DATA: "special_test" },
},
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.strictEqual(binEnv["DATA"], "rundata");
@ -84,15 +84,15 @@ export async function getTests(ctx: Context) {
assert.strictEqual(specialTestEnv["DATA"], "special_test");
});
suite.addTest('exact test name works', async () => {
suite.addTest("exact test name works", async () => {
const config = [
{
env: { DATA: "data" }
env: { DATA: "data" },
},
{
mask: "some::mod::test_name",
env: { DATA: "test special" }
}
env: { DATA: "test special" },
},
];
const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.strictEqual(testEnv["DATA"], "test special");
@ -101,15 +101,15 @@ export async function getTests(ctx: Context) {
assert.strictEqual(specialTestEnv["DATA"], "data");
});
suite.addTest('test mod name works', async () => {
suite.addTest("test mod name works", async () => {
const config = [
{
env: { DATA: "data" }
env: { DATA: "data" },
},
{
mask: "some::mod",
env: { DATA: "mod special" }
}
env: { DATA: "mod special" },
},
];
const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.strictEqual(testEnv["DATA"], "mod special");

View file

@ -1,30 +1,30 @@
import * as assert from 'assert';
import { Context } from '.';
import { substituteVariablesInEnv } from '../../src/config';
import * as assert from "assert";
import { Context } from ".";
import { substituteVariablesInEnv } from "../../src/config";
export async function getTests(ctx: Context) {
await ctx.suite('Server Env Settings', suite => {
suite.addTest('Replacing Env Variables', async () => {
await ctx.suite("Server Env Settings", (suite) => {
suite.addTest("Replacing Env Variables", async () => {
const envJson = {
USING_MY_VAR: "${env:MY_VAR} test ${env:MY_VAR}",
MY_VAR: "test"
MY_VAR: "test",
};
const expectedEnv = {
USING_MY_VAR: "test test test",
MY_VAR: "test"
MY_VAR: "test",
};
const actualEnv = await substituteVariablesInEnv(envJson);
assert.deepStrictEqual(actualEnv, expectedEnv);
});
suite.addTest('Circular dependencies remain as is', async () => {
suite.addTest("Circular dependencies remain as is", async () => {
const envJson = {
A_USES_B: "${env:B_USES_A}",
B_USES_A: "${env:A_USES_B}",
C_USES_ITSELF: "${env:C_USES_ITSELF}",
D_USES_C: "${env:C_USES_ITSELF}",
E_IS_ISOLATED: "test",
F_USES_E: "${env:E_IS_ISOLATED}"
F_USES_E: "${env:E_IS_ISOLATED}",
};
const expectedEnv = {
A_USES_B: "${env:B_USES_A}",
@ -32,30 +32,30 @@ export async function getTests(ctx: Context) {
C_USES_ITSELF: "${env:C_USES_ITSELF}",
D_USES_C: "${env:C_USES_ITSELF}",
E_IS_ISOLATED: "test",
F_USES_E: "test"
F_USES_E: "test",
};
const actualEnv = await substituteVariablesInEnv(envJson);
assert.deepStrictEqual(actualEnv, expectedEnv);
});
suite.addTest('Should support external variables', async () => {
suite.addTest("Should support external variables", async () => {
const envJson = {
USING_EXTERNAL_VAR: "${env:TEST_VARIABLE} test ${env:TEST_VARIABLE}"
USING_EXTERNAL_VAR: "${env:TEST_VARIABLE} test ${env:TEST_VARIABLE}",
};
const expectedEnv = {
USING_EXTERNAL_VAR: "test test test"
USING_EXTERNAL_VAR: "test test test",
};
const actualEnv = await substituteVariablesInEnv(envJson);
assert.deepStrictEqual(actualEnv, expectedEnv);
});
suite.addTest('should support VSCode variables', async () => {
suite.addTest("should support VSCode variables", async () => {
const envJson = {
USING_VSCODE_VAR: "${workspaceFolderBasename}"
USING_VSCODE_VAR: "${workspaceFolderBasename}",
};
const actualEnv = await substituteVariablesInEnv(envJson);
assert.deepStrictEqual(actualEnv.USING_VSCODE_VAR, 'code');
assert.deepStrictEqual(actualEnv.USING_VSCODE_VAR, "code");
});
});
}

View file

@ -1,11 +1,11 @@
// Special typescript project file, used by eslint only.
{
"extends": "./tsconfig.json",
"include": [
// repeated from base config's "include" setting
"src",
"tests",
// these are the eslint-only inclusions
".eslintrc.js",
]
"extends": "./tsconfig.json",
"include": [
// repeated from base config's "include" setting
"src",
"tests",
// these are the eslint-only inclusions
".eslintrc.js"
]
}

View file

@ -3,9 +3,7 @@
"module": "commonjs",
"target": "es2021",
"outDir": "out",
"lib": [
"es2021"
],
"lib": ["es2021"],
"sourceMap": true,
"rootDir": ".",
"strict": true,
@ -16,12 +14,6 @@
"noFallthroughCasesInSwitch": true,
"newLine": "LF"
},
"exclude": [
"node_modules",
".vscode-test"
],
"include": [
"src",
"tests"
]
"exclude": ["node_modules", ".vscode-test"],
"include": ["src", "tests"]
}