Skip to content
This repository was archived by the owner on Jul 31, 2023. It is now read-only.
Closed
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
3 changes: 2 additions & 1 deletion packages/language-server-ruby/src/util/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export function spawn<T = string>(
if (typeof b === 'string') {
chunk = b.toString();
} else {
chunk = b.toString(optsWithoutStdIn.encoding || 'utf8');
// chunk = b.toString(optsWithoutStdIn.encoding || 'utf8'); // didnt work for me
chunk = b.toString();
}
} catch (e) {
chunk = `<< Lost chunk of process output for ${cmd} - length was ${b.length}>>`;
Expand Down
4 changes: 3 additions & 1 deletion packages/vscode-ruby-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
"lodash": "^4.17.15",
"minimatch": "^3.0.4",
"ruby-method-locate": "^0.0.6",
"vscode-languageclient": "^5.2.1"
"vscode-languageclient": "^5.2.1",
"vscode-oniguruma": "^1.4.0",
"vscode-textmate": "^5.2.0"
},
"devDependencies": {
"@types/execa": "^2.0.0",
Expand Down
176 changes: 132 additions & 44 deletions packages/vscode-ruby-client/src/providers/highlight.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,137 @@
import * as vscode from 'vscode';
import { DocumentSelector, ExtensionContext } from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import * as vsctm from 'vscode-textmate';
import * as oniguruma from 'vscode-oniguruma';

export function registerHighlightProvider(ctx: ExtensionContext, documentSelector: DocumentSelector) {
// highlight provider
let pairedEnds = [];
const pairs = {
'keyword.control.class.begin.ruby': 'keyword.control.class.end.ruby',
'keyword.control.module.begin.ruby': 'keyword.control.module.end.ruby',
'keyword.control.do.begin.ruby': 'keyword.control.do.end.ruby',
'keyword.control.for.begin.ruby': 'keyword.control.for.end.ruby',
'keyword.control.begin.begin.ruby': 'keyword.control.begin.end.ruby',
'keyword.control.conditional.case.begin.ruby': 'keyword.control.conditional.case.end.ruby',
'keyword.control.conditional.if.begin.ruby': 'keyword.control.conditional.if.end.ruby',
'keyword.control.conditional.unless.begin.ruby': 'keyword.control.conditional.unless.end.ruby',
'keyword.control.while.begin.ruby': 'keyword.control.while.end.ruby',
'keyword.control.until.begin.ruby': 'keyword.control.until.end.ruby',
'keyword.control.def.begin.ruby': 'keyword.control.def.end.ruby',
};

const getEnd = function (line) {
//end must be on a line by itself, or followed directly by a dot
let match = line.text.match(/^(\s*)end\b[\.\s#]?\s*$/);
if (match) {
return new vscode.Range(line.lineNumber, match[1].length, line.lineNumber, match[1].length + 3);
}
}
const entries: string[] = Object.keys(pairs);

const getEntry = function(line) {
let match = line.text.match(/^(.*\b)(begin|class|def|for|if|module|unless|until|case|while)\b[^;]*$/);
if (match) {
return new vscode.Range(line.lineNumber, match[1].length, line.lineNumber, match[1].length + match[2].length);
} else {
//check for do
match = line.text.match(/\b(do)\b\s*(\|.*\|[^;]*)?$/);
if (match) {
return new vscode.Range(line.lineNumber, match.index, line.lineNumber, match.index + 2);
}
const wasmBin = fs.readFileSync(
path.join(__dirname, '../../node_modules/vscode-oniguruma/release/onig.wasm')
).buffer;

const vscodeOnigurumaLib = oniguruma.loadWASM(wasmBin).then(() => {
return {
createOnigScanner(patterns: string[]): oniguruma.OnigScanner {
return new oniguruma.OnigScanner(patterns);
},
createOnigString(s: string): oniguruma.OnigString {
return new oniguruma.OnigString(s);
},
};
});

const registry: vsctm.Registry = new vsctm.Registry({
onigLib: vscodeOnigurumaLib,
loadGrammar: (scopeName: string) => {
if (scopeName === 'source.ruby') {
let grammarPath: string = path.resolve(
__dirname,
'../../../vscode-ruby/syntaxes/',
'ruby.cson.json'
);
return readFile(grammarPath).then(data =>
vsctm.parseRawGrammar(data.toString(), grammarPath)
);
}
}
console.log(`Unknown scope name: ${scopeName}`);
return null;
},
});

export function registerHighlightProvider(
ctx: ExtensionContext,
documentSelector: DocumentSelector
) {
// highlight provider
let pairedEnds = [];
let grammar = null;

const balancePairs = function (doc) {
const balancePairs = async function(doc) {
pairedEnds = [];
if (doc.languageId !== 'ruby') return;

let waitingEntries = [];
let entry, end;

if (!grammar) {
grammar = await registry.loadGrammar('source.ruby');
}

let ruleStack = vsctm.INITIAL;
for (let i = 0; i < doc.lineCount; i++) {
if ((entry = getEntry(doc.lineAt(i)))) {
waitingEntries.push(entry);
} else if (waitingEntries.length && (end = getEnd(doc.lineAt(i)))) {
pairedEnds.push({
entry: waitingEntries.pop(),
end: end
});
const line = doc.lineAt(i);

const lineTokens = grammar.tokenizeLine(line.text, ruleStack);

for (let j = 0; j < lineTokens.tokens.length; j++) {
const token = lineTokens.tokens[j];

let entryScope = token.scopes.find(scope => entries.some(e => e == scope));
if (entryScope) {
waitingEntries.push({
range: new vscode.Range(
line.lineNumber,
token.startIndex,
line.lineNumber,
token.endIndex
),
scope: entryScope,
});
continue;
}

if (waitingEntries.length) {
const lastEntryScope = waitingEntries[waitingEntries.length - 1].scope;
if (token.scopes.find(scope => scope == pairs[lastEntryScope])) {
pairedEnds.push({
entry: waitingEntries.pop().range,
end: new vscode.Range(
line.lineNumber,
token.startIndex,
line.lineNumber,
token.endIndex
),
});
}
}
}
ruleStack = lineTokens.ruleStack;
}
}
};

const balanceEvent = function (event) {
const balanceEvent = function(event) {
if (event && event.document) balancePairs(event.document);
}
};

ctx.subscriptions.push(vscode.languages.registerDocumentHighlightProvider(documentSelector, {
provideDocumentHighlights: (doc, pos) => {
let result = pairedEnds.find(pair => (
pair.entry.start.line === pos.line ||
pair.end.start.line === pos.line));
if (result) {
return [new vscode.DocumentHighlight(result.entry, 2), new vscode.DocumentHighlight(result.end, 2)];
}
}
}));
ctx.subscriptions.push(
vscode.languages.registerDocumentHighlightProvider(documentSelector, {
provideDocumentHighlights: (doc, pos) => {
let result = pairedEnds.find(
pair => posWithinRange(pos, pair.entry) || posWithinRange(pos, pair.end)
);
if (result) {
return [
new vscode.DocumentHighlight(result.entry, 2),
new vscode.DocumentHighlight(result.end, 2),
];
}
},
})
);

ctx.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(balanceEvent));
ctx.subscriptions.push(vscode.workspace.onDidChangeTextDocument(balanceEvent));
Expand All @@ -66,3 +140,17 @@ export function registerHighlightProvider(ctx: ExtensionContext, documentSelecto
balancePairs(vscode.window.activeTextEditor.document);
}
}

function posWithinRange(pos, range) {
return (
range.start.line === pos.line &&
range.start.character <= pos.character &&
range.end.character >= pos.character
);
}

function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (error, data) => (error ? reject(error) : resolve(data)));
});
}
Loading