Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit e5c611f

Browse files
authored
Merge pull request #38 from xeger/execPath
Let users specify path to stree
2 parents 8674627 + 24d5425 commit e5c611f

File tree

3 files changed

+188
-131
lines changed

3 files changed

+188
-131
lines changed

package.json

Lines changed: 123 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,124 @@
11
{
2-
"name": "vscode-syntax-tree",
3-
"displayName": "Syntax Tree",
4-
"description": "VSCode support for the syntax_tree gem",
5-
"icon": "doc/logo.png",
6-
"version": "0.3.1",
7-
"publisher": "ruby-syntax-tree",
8-
"repository": {
9-
"type": "git",
10-
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree.git"
11-
},
12-
"license": "MIT",
13-
"bugs": {
14-
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree/issues"
15-
},
16-
"engines": {
17-
"vscode": "^1.68.0"
18-
},
19-
"activationEvents": [
20-
"onLanguage:ruby",
21-
"workspaceContains:Gemfile.lock",
22-
"onCommand:syntaxTree.start",
23-
"onCommand:syntaxTree.stop",
24-
"onCommand:syntaxTree.restart",
25-
"onCommand:syntaxTree.showOutputChannel",
26-
"onCommand:syntaxTree.visualize"
27-
],
28-
"main": "./out/extension",
29-
"contributes": {
30-
"commands": [
31-
{
32-
"command": "syntaxTree.start",
33-
"title": "Syntax Tree: Start"
34-
},
35-
{
36-
"command": "syntaxTree.stop",
37-
"title": "Syntax Tree: Stop"
38-
},
39-
{
40-
"command": "syntaxTree.restart",
41-
"title": "Syntax Tree: Restart"
42-
},
43-
{
44-
"command": "syntaxTree.showOutputChannel",
45-
"title": "Syntax Tree: Show Output Channel"
46-
},
47-
{
48-
"command": "syntaxTree.visualize",
49-
"title": "Syntax Tree: Visualize"
50-
}
51-
],
52-
"configuration": {
53-
"type": "object",
54-
"title": "Syntax Tree",
55-
"properties": {
56-
"syntaxTree.printWidth": {
57-
"default": 80,
58-
"markdownDescription": "The width to be used when formatting code.",
59-
"type": "number"
60-
},
61-
"syntaxTree.singleQuotes": {
62-
"default": false,
63-
"markdownDescription": "Uses single-quoted strings when possible.",
64-
"type": "boolean"
65-
},
66-
"syntaxTree.trailingComma": {
67-
"default": false,
68-
"markdownDescription": "Adds a trailing comma to multi-line array literals, hash literals, and method parameters.",
69-
"type": "boolean"
70-
},
71-
"syntaxTree.additionalPlugins": {
72-
"default": [],
73-
"markdownDescription": "Registers [extra behaviors](https://github.com/ruby-syntax-tree/syntax_tree#plugins) with the language server.",
74-
"items": {
75-
"type": "string"
76-
},
77-
"type": "array"
78-
}
79-
}
80-
},
81-
"colors": []
82-
},
83-
"scripts": {
84-
"compile": "tsc -p ./",
85-
"package": "vsce package --no-yarn --githubBranch main",
86-
"publish": "vsce publish --no-yarn --githubBranch main",
87-
"test": "node ./out/test/runTest.js",
88-
"vscode:prepublish": "yarn compile",
89-
"watch": "tsc --watch -p ./"
90-
},
91-
"dependencies": {
92-
"vscode-languageclient": "8.0.2"
93-
},
94-
"devDependencies": {
95-
"@types/glob": "^7.1.1",
96-
"@types/mocha": "^9.1.1",
97-
"@types/node": "^18.0.0",
98-
"@types/vscode": "^1.68.0",
99-
"@vscode/test-electron": "^1.6.2",
100-
"glob": "^8.0.3",
101-
"mocha": "^10.0.0",
102-
"typescript": "^4.7.4",
103-
"vsce": "^2.9.2"
104-
},
105-
"__metadata": {
106-
"id": "b46118f9-0f6f-4320-9e2e-75c96492b4cb",
107-
"publisherDisplayName": "ruby-syntax-tree",
108-
"publisherId": "63942dce-de09-44d8-b863-4a1dbd5508c6",
109-
"isPreReleaseVersion": false
110-
}
111-
}
2+
"name": "vscode-syntax-tree",
3+
"displayName": "Syntax Tree",
4+
"description": "VSCode support for the syntax_tree gem",
5+
"icon": "doc/logo.png",
6+
"version": "0.3.1",
7+
"publisher": "ruby-syntax-tree",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree.git"
11+
},
12+
"license": "MIT",
13+
"bugs": {
14+
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree/issues"
15+
},
16+
"engines": {
17+
"vscode": "^1.68.0"
18+
},
19+
"activationEvents": [
20+
"onLanguage:ruby",
21+
"workspaceContains:Gemfile.lock",
22+
"onCommand:syntaxTree.start",
23+
"onCommand:syntaxTree.stop",
24+
"onCommand:syntaxTree.restart",
25+
"onCommand:syntaxTree.showOutputChannel",
26+
"onCommand:syntaxTree.visualize"
27+
],
28+
"main": "./out/extension",
29+
"contributes": {
30+
"commands": [
31+
{
32+
"command": "syntaxTree.start",
33+
"title": "Syntax Tree: Start"
34+
},
35+
{
36+
"command": "syntaxTree.stop",
37+
"title": "Syntax Tree: Stop"
38+
},
39+
{
40+
"command": "syntaxTree.restart",
41+
"title": "Syntax Tree: Restart"
42+
},
43+
{
44+
"command": "syntaxTree.showOutputChannel",
45+
"title": "Syntax Tree: Show Output Channel"
46+
},
47+
{
48+
"command": "syntaxTree.visualize",
49+
"title": "Syntax Tree: Visualize"
50+
}
51+
],
52+
"configuration": [
53+
{
54+
"type": "object",
55+
"title": "Syntax Tree",
56+
"properties": {
57+
"syntaxTree.additionalPlugins": {
58+
"default": [],
59+
"markdownDescription": "Registers [extra behaviors](https://github.com/ruby-syntax-tree/syntax_tree#plugins) with the language server.",
60+
"items": {
61+
"type": "string"
62+
},
63+
"type": "array"
64+
},
65+
"syntaxTree.printWidth": {
66+
"default": 80,
67+
"markdownDescription": "The width to be used when formatting code.",
68+
"type": "number"
69+
},
70+
"syntaxTree.singleQuotes": {
71+
"default": false,
72+
"markdownDescription": "Uses single-quoted strings when possible.",
73+
"type": "boolean"
74+
},
75+
"syntaxTree.trailingComma": {
76+
"default": false,
77+
"markdownDescription": "Adds a trailing comma to multi-line array literals, hash literals, and method parameters.",
78+
"type": "boolean"
79+
}
80+
}
81+
},
82+
{
83+
"type": "object",
84+
"title": "Advanced",
85+
"properties": {
86+
"syntaxTree.advanced.commandPath": {
87+
"default": "",
88+
"markdownDescription": "Absolute path to stree executable. Overrides default search order.\n\nSupports variables `${userHome}`, `${pathSeparator}`, and `${cwd}`",
89+
"type": "string"
90+
}
91+
}
92+
}
93+
],
94+
"colors": []
95+
},
96+
"scripts": {
97+
"compile": "tsc -p ./",
98+
"package": "vsce package --no-yarn --githubBranch main",
99+
"publish": "vsce publish --no-yarn --githubBranch main",
100+
"test": "node ./out/test/runTest.js",
101+
"vscode:prepublish": "yarn compile",
102+
"watch": "tsc --watch -p ./"
103+
},
104+
"dependencies": {
105+
"vscode-languageclient": "8.0.2"
106+
},
107+
"devDependencies": {
108+
"@types/glob": "^7.1.1",
109+
"@types/mocha": "^9.1.1",
110+
"@types/node": "^18.0.0",
111+
"@types/vscode": "^1.68.0",
112+
"@vscode/test-electron": "^1.6.2",
113+
"glob": "^8.0.3",
114+
"mocha": "^10.0.0",
115+
"typescript": "^4.7.4",
116+
"vsce": "^2.9.2"
117+
},
118+
"__metadata": {
119+
"id": "b46118f9-0f6f-4320-9e2e-75c96492b4cb",
120+
"publisherDisplayName": "ruby-syntax-tree",
121+
"publisherId": "63942dce-de09-44d8-b863-4a1dbd5508c6",
122+
"isPreReleaseVersion": false
123+
}
124+
}

src/extension.ts

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"use strict";
22

3+
import { exec } from "child_process";
4+
import * as fs from "fs";
5+
import { promisify } from "util";
36
import { ExtensionContext, commands, window, workspace } from "vscode";
47
import { LanguageClient, ServerOptions } from "vscode-languageclient/node";
5-
import { promisify } from "util";
6-
import { exec } from "child_process";
78

9+
import * as variables from './variables';
810
import Visualize from "./Visualize";
911

1012
const promiseExec = promisify(exec);
@@ -59,9 +61,11 @@ export async function activate(context: ExtensionContext) {
5961
return; // preserve idempotency
6062
}
6163

62-
// The top-level configuration group is syntaxTree. All of the configuration
63-
// for the extension is under that group.
64+
// The top-level configuration group is syntaxTree. Broadly useful settings
65+
// are under that group.
6466
const config = workspace.getConfiguration("syntaxTree");
67+
// More obscure settings for power users live in a subgroup.
68+
const advancedConfig = workspace.getConfiguration("syntaxTree.advanced");
6569

6670
// The args are going to be passed to the stree executable. It's important
6771
// that it lines up with what the CLI expects.
@@ -90,26 +94,41 @@ export async function activate(context: ExtensionContext) {
9094
// Configure print width.
9195
args.push(`--print-width=${config.get<number>("printWidth")}`)
9296

93-
// There's a bit of complexity here. Basically, if there's an open folder,
94-
// then we're going to check if the syntax_tree gem is inside the bundle. If
95-
// it is, then we'll run bundle exec stree. This is good, because it'll
96-
// ensure that we get the correct version of the gem. If it's not in the
97-
// bundle or there is no bundle, then we'll just run the global stree. This
98-
// might be correct in the end if the right environment variables are set,
99-
// but it's a bit of a prayer.
100-
const cwd = getCWD();
97+
// There's a bit of complexity here. Basically, we try to locate
98+
// an stree executable in three places, in order of preference:
99+
// 1. Explicit path from advanced settings, if provided
100+
// 2. The bundle inside CWD, if syntax_tree is in the bundle
101+
// 3. Anywhere in $PATH (i.e. system gem)
102+
//
103+
// None of these approaches is perfect. System gem might be correct if the
104+
// right environment variables are set, but it's a bit of a prayer. Bundled
105+
// gem is better, but we make the gross oversimplification that the
106+
// workspace only has one root and that the bundle is at root of the
107+
// workspace -- which is not true for large projects or monorepos.
108+
// Explicit path varies between machines/users and is also victim to the
109+
// oversimplification problem.
101110
let run: ServerOptions = { command: "stree", args };
102-
let where = 'global';
103-
104-
try {
105-
await promiseExec("bundle show syntax_tree", { cwd });
106-
run = { command: "bundle", args: ["exec", "stree"].concat(args), options: { cwd } };
107-
where = 'bundled';
108-
} catch {
109-
// No-op (just keep using the global stree)
111+
let commandPath = advancedConfig.get<string>('commandPath');
112+
if (commandPath) {
113+
commandPath = variables.substitute(commandPath);
114+
try {
115+
if (fs.statSync(commandPath).isFile()) {
116+
run = { command: commandPath, args };
117+
}
118+
} catch (err) {
119+
outputChannel.appendLine(`Ignoring bogus commandPath (${commandPath} does not exist); falling back to global.`);
120+
}
121+
} else {
122+
try {
123+
const cwd = getCWD();
124+
await promiseExec("bundle show syntax_tree", { cwd });
125+
run = { command: "bundle", args: ["exec", "stree"].concat(args), options: { cwd } };
126+
} catch {
127+
// No-op (just keep using the global stree)
128+
}
110129
}
111130

112-
outputChannel.appendLine(`Starting language server with ${where} stree and ${plugins.size} plugin(s)...`);
131+
outputChannel.appendLine(`Starting language server: ${run.command} ${run.args?.join(' ')}`);
113132

114133
// Here, we instantiate the language client. This is the object that is
115134
// responsible for the communication and management of the Ruby subprocess.

src/variables.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as os from 'os';
2+
import * as path from 'path';
3+
4+
const substitution = new RegExp('\\$\\{([^}]*)\\}');
5+
6+
export function substitute(s: string) {
7+
let match = substitution.exec(s);
8+
while (match) {
9+
const variable = match[1];
10+
switch (variable) {
11+
case 'cwd':
12+
s = s.replace(match[0], process.cwd());
13+
break;
14+
case 'pathSeparator':
15+
s = s.replace(match[0], path.sep);
16+
break;
17+
case 'userHome':
18+
s = s.replace(match[0], os.homedir());
19+
break;
20+
}
21+
match = substitution.exec(s);
22+
}
23+
24+
return s;
25+
}

0 commit comments

Comments
 (0)