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

Let users specify path to stree #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 123 additions & 110 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,111 +1,124 @@
{
"name": "vscode-syntax-tree",
"displayName": "Syntax Tree",
"description": "VSCode support for the syntax_tree gem",
"icon": "doc/logo.png",
"version": "0.3.1",
"publisher": "ruby-syntax-tree",
"repository": {
"type": "git",
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree/issues"
},
"engines": {
"vscode": "^1.68.0"
},
"activationEvents": [
"onLanguage:ruby",
"workspaceContains:Gemfile.lock",
"onCommand:syntaxTree.start",
"onCommand:syntaxTree.stop",
"onCommand:syntaxTree.restart",
"onCommand:syntaxTree.showOutputChannel",
"onCommand:syntaxTree.visualize"
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "syntaxTree.start",
"title": "Syntax Tree: Start"
},
{
"command": "syntaxTree.stop",
"title": "Syntax Tree: Stop"
},
{
"command": "syntaxTree.restart",
"title": "Syntax Tree: Restart"
},
{
"command": "syntaxTree.showOutputChannel",
"title": "Syntax Tree: Show Output Channel"
},
{
"command": "syntaxTree.visualize",
"title": "Syntax Tree: Visualize"
}
],
"configuration": {
"type": "object",
"title": "Syntax Tree",
"properties": {
"syntaxTree.printWidth": {
"default": 80,
"markdownDescription": "The width to be used when formatting code.",
"type": "number"
},
"syntaxTree.singleQuotes": {
"default": false,
"markdownDescription": "Uses single-quoted strings when possible.",
"type": "boolean"
},
"syntaxTree.trailingComma": {
"default": false,
"markdownDescription": "Adds a trailing comma to multi-line array literals, hash literals, and method parameters.",
"type": "boolean"
},
"syntaxTree.additionalPlugins": {
"default": [],
"markdownDescription": "Registers [extra behaviors](https://github.com/ruby-syntax-tree/syntax_tree#plugins) with the language server.",
"items": {
"type": "string"
},
"type": "array"
}
}
},
"colors": []
},
"scripts": {
"compile": "tsc -p ./",
"package": "vsce package --no-yarn --githubBranch main",
"publish": "vsce publish --no-yarn --githubBranch main",
"test": "node ./out/test/runTest.js",
"vscode:prepublish": "yarn compile",
"watch": "tsc --watch -p ./"
},
"dependencies": {
"vscode-languageclient": "8.0.2"
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/mocha": "^9.1.1",
"@types/node": "^18.0.0",
"@types/vscode": "^1.68.0",
"@vscode/test-electron": "^1.6.2",
"glob": "^8.0.3",
"mocha": "^10.0.0",
"typescript": "^4.7.4",
"vsce": "^2.9.2"
},
"__metadata": {
"id": "b46118f9-0f6f-4320-9e2e-75c96492b4cb",
"publisherDisplayName": "ruby-syntax-tree",
"publisherId": "63942dce-de09-44d8-b863-4a1dbd5508c6",
"isPreReleaseVersion": false
}
}
"name": "vscode-syntax-tree",
"displayName": "Syntax Tree",
"description": "VSCode support for the syntax_tree gem",
"icon": "doc/logo.png",
"version": "0.3.1",
"publisher": "ruby-syntax-tree",
"repository": {
"type": "git",
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/ruby-syntax-tree/vscode-syntax-tree/issues"
},
"engines": {
"vscode": "^1.68.0"
},
"activationEvents": [
"onLanguage:ruby",
"workspaceContains:Gemfile.lock",
"onCommand:syntaxTree.start",
"onCommand:syntaxTree.stop",
"onCommand:syntaxTree.restart",
"onCommand:syntaxTree.showOutputChannel",
"onCommand:syntaxTree.visualize"
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "syntaxTree.start",
"title": "Syntax Tree: Start"
},
{
"command": "syntaxTree.stop",
"title": "Syntax Tree: Stop"
},
{
"command": "syntaxTree.restart",
"title": "Syntax Tree: Restart"
},
{
"command": "syntaxTree.showOutputChannel",
"title": "Syntax Tree: Show Output Channel"
},
{
"command": "syntaxTree.visualize",
"title": "Syntax Tree: Visualize"
}
],
"configuration": [
{
"type": "object",
"title": "Syntax Tree",
"properties": {
"syntaxTree.additionalPlugins": {
"default": [],
"markdownDescription": "Registers [extra behaviors](https://github.com/ruby-syntax-tree/syntax_tree#plugins) with the language server.",
"items": {
"type": "string"
},
"type": "array"
},
"syntaxTree.printWidth": {
"default": 80,
"markdownDescription": "The width to be used when formatting code.",
"type": "number"
},
"syntaxTree.singleQuotes": {
"default": false,
"markdownDescription": "Uses single-quoted strings when possible.",
"type": "boolean"
},
"syntaxTree.trailingComma": {
"default": false,
"markdownDescription": "Adds a trailing comma to multi-line array literals, hash literals, and method parameters.",
"type": "boolean"
}
}
},
{
"type": "object",
"title": "Advanced",
"properties": {
"syntaxTree.advanced.commandPath": {
"default": "",
"markdownDescription": "Absolute path to stree executable. Overrides default search order.\n\nSupports variables `${userHome}`, `${pathSeparator}`, and `${cwd}`",
"type": "string"
}
}
}
],
"colors": []
},
"scripts": {
"compile": "tsc -p ./",
"package": "vsce package --no-yarn --githubBranch main",
"publish": "vsce publish --no-yarn --githubBranch main",
"test": "node ./out/test/runTest.js",
"vscode:prepublish": "yarn compile",
"watch": "tsc --watch -p ./"
},
"dependencies": {
"vscode-languageclient": "8.0.2"
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/mocha": "^9.1.1",
"@types/node": "^18.0.0",
"@types/vscode": "^1.68.0",
"@vscode/test-electron": "^1.6.2",
"glob": "^8.0.3",
"mocha": "^10.0.0",
"typescript": "^4.7.4",
"vsce": "^2.9.2"
},
"__metadata": {
"id": "b46118f9-0f6f-4320-9e2e-75c96492b4cb",
"publisherDisplayName": "ruby-syntax-tree",
"publisherId": "63942dce-de09-44d8-b863-4a1dbd5508c6",
"isPreReleaseVersion": false
}
}
61 changes: 40 additions & 21 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"use strict";

import { exec } from "child_process";
import * as fs from "fs";
import { promisify } from "util";
import { ExtensionContext, commands, window, workspace } from "vscode";
import { LanguageClient, ServerOptions } from "vscode-languageclient/node";
import { promisify } from "util";
import { exec } from "child_process";

import * as variables from './variables';
import Visualize from "./Visualize";

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

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

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

// There's a bit of complexity here. Basically, if there's an open folder,
// then we're going to check if the syntax_tree gem is inside the bundle. If
// it is, then we'll run bundle exec stree. This is good, because it'll
// ensure that we get the correct version of the gem. If it's not in the
// bundle or there is no bundle, then we'll just run the global stree. This
// might be correct in the end if the right environment variables are set,
// but it's a bit of a prayer.
const cwd = getCWD();
// There's a bit of complexity here. Basically, we try to locate
// an stree executable in three places, in order of preference:
// 1. Explicit path from advanced settings, if provided
// 2. The bundle inside CWD, if syntax_tree is in the bundle
// 3. Anywhere in $PATH (i.e. system gem)
//
// None of these approaches is perfect. System gem might be correct if the
// right environment variables are set, but it's a bit of a prayer. Bundled
// gem is better, but we make the gross oversimplification that the
// workspace only has one root and that the bundle is at root of the
// workspace -- which is not true for large projects or monorepos.
// Explicit path varies between machines/users and is also victim to the
// oversimplification problem.
let run: ServerOptions = { command: "stree", args };
let where = 'global';

try {
await promiseExec("bundle show syntax_tree", { cwd });
run = { command: "bundle", args: ["exec", "stree"].concat(args), options: { cwd } };
where = 'bundled';
} catch {
// No-op (just keep using the global stree)
let commandPath = advancedConfig.get<string>('commandPath');
if (commandPath) {
commandPath = variables.substitute(commandPath);
try {
if (fs.statSync(commandPath).isFile()) {
run = { command: commandPath, args };
}
} catch (err) {
outputChannel.appendLine(`Ignoring bogus commandPath (${commandPath} does not exist); falling back to global.`);
}
} else {
try {
const cwd = getCWD();
await promiseExec("bundle show syntax_tree", { cwd });
run = { command: "bundle", args: ["exec", "stree"].concat(args), options: { cwd } };
} catch {
// No-op (just keep using the global stree)
}
}

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

// Here, we instantiate the language client. This is the object that is
// responsible for the communication and management of the Ruby subprocess.
Expand Down
25 changes: 25 additions & 0 deletions src/variables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as os from 'os';
import * as path from 'path';

const substitution = new RegExp('\\$\\{([^}]*)\\}');

export function substitute(s: string) {
let match = substitution.exec(s);
while (match) {
const variable = match[1];
switch (variable) {
case 'cwd':
s = s.replace(match[0], process.cwd());
break;
case 'pathSeparator':
s = s.replace(match[0], path.sep);
break;
case 'userHome':
s = s.replace(match[0], os.homedir());
break;
}
match = substitution.exec(s);
}

return s;
}