104: Add vscode extension to CI r=aochagavia a=DJMcNab

Note that this testing is only done on travis - we are only running formatting and linting, so feature parity on appveyor is not required.

CC @aochagavia.

Fixes? #100

Co-authored-by: Daniel McNab <36049421+djmcnab@users.noreply.github.com>
This commit is contained in:
bors[bot] 2018-10-09 06:16:36 +00:00
commit c9798c0e6d
25 changed files with 803 additions and 492 deletions

View file

@ -1,19 +1,24 @@
language: rust
matrix:
include:
- rust: stable
- language: rust
rust: stable
script:
- cargo gen-kinds --verify
- cargo gen-tests --verify
- cargo test
# - rust: nightly
# - language: rust
# rust: nightly
# before_script:
# - rustup component add clippy-preview
# - rustup component add rustfmt-preview
# script:
# - cargo fmt --all -- --check || true
# - cargo clippy
- language: node_js
node_js: node
before_script: false
script:
- cd editors/code && npm ci && npm run travis; cd ../..
allow_failures:
- rust nightly

View file

@ -3,19 +3,15 @@
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"version": "0.2.0",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "npm: watch"
},
{
@ -27,9 +23,7 @@
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
],
"outFiles": ["${workspaceFolder}/out/test/**/*.js"],
"preLaunchTask": "npm: watch"
}
]

View file

@ -8,4 +8,4 @@
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}
}

View file

@ -17,4 +17,4 @@
}
}
]
}
}

File diff suppressed because it is too large Load diff

View file

@ -17,17 +17,27 @@
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install"
"postinstall": "node ./node_modules/vscode/bin/install",
"lint": "tslint --project .",
"prettier": "prettier **/*.{json,ts}",
"travis": "npm run compile && npm run lint && npm run prettier --list-different"
},
"prettier": {
"tabWidth": 4,
"singleQuote": true
},
"dependencies": {
"vscode-languageclient": "^4.4.0"
},
"devDependencies": {
"@types/mocha": "^2.2.42",
"@types/node": "^8.10.34",
"prettier": "^1.14.3",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0",
"typescript": "^2.6.1",
"vscode": "^1.1.21",
"vsce": "^1.42.0",
"@types/node": "^8.10.25",
"@types/mocha": "^2.2.42"
"vsce": "^1.51.1",
"vscode": "^1.1.21"
},
"activationEvents": [
"onLanguage:rust"

View file

@ -20,8 +20,12 @@ export interface SourceChange {
export async function handle(change: SourceChange) {
const wsEdit = new vscode.WorkspaceEdit();
for (const sourceEdit of change.sourceFileEdits) {
const uri = Server.client.protocol2CodeConverter.asUri(sourceEdit.textDocument.uri);
const edits = Server.client.protocol2CodeConverter.asTextEdits(sourceEdit.edits);
const uri = Server.client.protocol2CodeConverter.asUri(
sourceEdit.textDocument.uri
);
const edits = Server.client.protocol2CodeConverter.asTextEdits(
sourceEdit.edits
);
wsEdit.set(uri, edits);
}
let created;
@ -48,11 +52,19 @@ export async function handle(change: SourceChange) {
const doc = await vscode.workspace.openTextDocument(toOpen);
await vscode.window.showTextDocument(doc);
} else if (toReveal) {
const uri = Server.client.protocol2CodeConverter.asUri(toReveal.textDocument.uri);
const position = Server.client.protocol2CodeConverter.asPosition(toReveal.position);
const uri = Server.client.protocol2CodeConverter.asUri(
toReveal.textDocument.uri
);
const position = Server.client.protocol2CodeConverter.asPosition(
toReveal.position
);
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.uri.toString() !== uri.toString()) { return; }
if (!editor.selection.isEmpty) { return; }
if (!editor || editor.document.uri.toString() !== uri.toString()) {
return;
}
if (!editor.selection.isEmpty) {
return;
}
editor!.selection = new vscode.Selection(position, position);
}
}

View file

@ -14,14 +14,19 @@ interface ExtendSelectionResult {
export async function handle() {
const editor = vscode.window.activeTextEditor;
if (editor == null || editor.document.languageId !== 'rust') { return; }
if (editor == null || editor.document.languageId !== 'rust') {
return;
}
const request: ExtendSelectionParams = {
selections: editor.selections.map((s) => {
selections: editor.selections.map(s => {
return Server.client.code2ProtocolConverter.asRange(s);
}),
textDocument: { uri: editor.document.uri.toString() },
textDocument: { uri: editor.document.uri.toString() }
};
const response = await Server.client.sendRequest<ExtendSelectionResult>('m/extendSelection', request);
const response = await Server.client.sendRequest<ExtendSelectionResult>(
'm/extendSelection',
request
);
editor.selections = response.selections.map((range: Range) => {
const r = Server.client.protocol2CodeConverter.asRange(range);
return new vscode.Selection(r.start, r.end);

View file

@ -13,5 +13,5 @@ export {
matchingBrace,
parentModule,
runnables,
syntaxTree,
syntaxTree
};

View file

@ -2,7 +2,10 @@ import * as vscode from 'vscode';
import { Range, TextDocumentIdentifier } from 'vscode-languageclient';
import { Server } from '../server';
import { handle as applySourceChange, SourceChange } from './apply_source_change';
import {
handle as applySourceChange,
SourceChange
} from './apply_source_change';
interface JoinLinesParams {
textDocument: TextDocumentIdentifier;
@ -11,11 +14,16 @@ interface JoinLinesParams {
export async function handle() {
const editor = vscode.window.activeTextEditor;
if (editor == null || editor.document.languageId !== 'rust') { return; }
if (editor == null || editor.document.languageId !== 'rust') {
return;
}
const request: JoinLinesParams = {
range: Server.client.code2ProtocolConverter.asRange(editor.selection),
textDocument: { uri: editor.document.uri.toString() },
textDocument: { uri: editor.document.uri.toString() }
};
const change = await Server.client.sendRequest<SourceChange>('m/joinLines', request);
const change = await Server.client.sendRequest<SourceChange>(
'm/joinLines',
request
);
await applySourceChange(change);
}

View file

@ -10,16 +10,23 @@ interface FindMatchingBraceParams {
export async function handle() {
const editor = vscode.window.activeTextEditor;
if (editor == null || editor.document.languageId !== 'rust') { return; }
if (editor == null || editor.document.languageId !== 'rust') {
return;
}
const request: FindMatchingBraceParams = {
textDocument: { uri: editor.document.uri.toString() },
offsets: editor.selections.map((s) => {
offsets: editor.selections.map(s => {
return Server.client.code2ProtocolConverter.asPosition(s.active);
}),
})
};
const response = await Server.client.sendRequest<Position[]>('m/findMatchingBrace', request);
const response = await Server.client.sendRequest<Position[]>(
'm/findMatchingBrace',
request
);
editor.selections = editor.selections.map((sel, idx) => {
const active = Server.client.protocol2CodeConverter.asPosition(response[idx]);
const active = Server.client.protocol2CodeConverter.asPosition(
response[idx]
);
const anchor = sel.isEmpty ? active : sel.anchor;
return new vscode.Selection(anchor, active);
});

View file

@ -5,13 +5,20 @@ import { Server } from '../server';
export async function handle() {
const editor = vscode.window.activeTextEditor;
if (editor == null || editor.document.languageId !== 'rust') { return; }
if (editor == null || editor.document.languageId !== 'rust') {
return;
}
const request: TextDocumentIdentifier = {
uri: editor.document.uri.toString(),
uri: editor.document.uri.toString()
};
const response = await Server.client.sendRequest<Location[]>('m/parentModule', request);
const response = await Server.client.sendRequest<Location[]>(
'm/parentModule',
request
);
const loc = response[0];
if (loc == null) { return; }
if (loc == null) {
return;
}
const uri = Server.client.protocol2CodeConverter.asUri(loc.uri);
const range = Server.client.protocol2CodeConverter.asRange(loc.range);

View file

@ -41,39 +41,56 @@ function createTask(spec: Runnable): vscode.Task {
label: 'cargo',
command: spec.bin,
args: spec.args,
env: spec.env,
env: spec.env
};
const execCmd = `${definition.command} ${definition.args.join(' ')}`;
const execOption: vscode.ShellExecutionOptions = {
cwd: '.',
env: definition.env,
env: definition.env
};
const exec = new vscode.ShellExecution(`clear; ${execCmd}`, execOption);
const f = vscode.workspace.workspaceFolders![0];
const t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
const t = new vscode.Task(
definition,
f,
definition.label,
TASK_SOURCE,
exec,
['$rustc']
);
return t;
}
let prevRunnable: RunnableQuickPick | undefined;
export async function handle() {
const editor = vscode.window.activeTextEditor;
if (editor == null || editor.document.languageId !== 'rust') { return; }
if (editor == null || editor.document.languageId !== 'rust') {
return;
}
const textDocument: lc.TextDocumentIdentifier = {
uri: editor.document.uri.toString(),
uri: editor.document.uri.toString()
};
const params: RunnablesParams = {
textDocument,
position: Server.client.code2ProtocolConverter.asPosition(editor.selection.active),
position: Server.client.code2ProtocolConverter.asPosition(
editor.selection.active
)
};
const runnables = await Server.client.sendRequest<Runnable[]>('m/runnables', params);
const runnables = await Server.client.sendRequest<Runnable[]>(
'm/runnables',
params
);
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;
}
items.push(new RunnableQuickPick(r));

View file

@ -5,17 +5,25 @@ import { Server } from '../server';
export const syntaxTreeUri = vscode.Uri.parse('ra-lsp://syntaxtree');
export class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
export class TextDocumentContentProvider
implements vscode.TextDocumentContentProvider {
public eventEmitter = new vscode.EventEmitter<vscode.Uri>();
public syntaxTree: string = 'Not available';
public provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> {
public provideTextDocumentContent(
uri: vscode.Uri
): vscode.ProviderResult<string> {
const editor = vscode.window.activeTextEditor;
if (editor == null) { return ''; }
if (editor == null) {
return '';
}
const request: SyntaxTreeParams = {
textDocument: { uri: editor.document.uri.toString() },
textDocument: { uri: editor.document.uri.toString() }
};
return Server.client.sendRequest<SyntaxTreeResult>('m/syntaxTree', request);
return Server.client.sendRequest<SyntaxTreeResult>(
'm/syntaxTree',
request
);
}
get onDidChange(): vscode.Event<vscode.Uri> {
@ -34,5 +42,9 @@ type SyntaxTreeResult = string;
// The contents of the file come from the `TextDocumentContentProvider`
export async function handle() {
const document = await vscode.workspace.openTextDocument(syntaxTreeUri);
return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
return vscode.window.showTextDocument(
document,
vscode.ViewColumn.Two,
true
);
}

View file

@ -6,7 +6,9 @@ export class Config {
public highlightingOn = true;
constructor() {
vscode.workspace.onDidChangeConfiguration((_) => this.userConfigChanged());
vscode.workspace.onDidChangeConfiguration(_ =>
this.userConfigChanged()
);
this.userConfigChanged();
}

View file

@ -5,10 +5,19 @@ import { Decoration } from '../highlighting';
import { Server } from '../server';
export async function handle(editor: TextEditor | undefined) {
if (!Server.config.highlightingOn || !editor || editor.document.languageId !== 'rust') { return; }
if (
!Server.config.highlightingOn ||
!editor ||
editor.document.languageId !== 'rust'
) {
return;
}
const params: TextDocumentIdentifier = {
uri: editor.document.uri.toString(),
uri: editor.document.uri.toString()
};
const decorations = await Server.client.sendRequest<Decoration[]>('m/decorationsRequest', params);
const decorations = await Server.client.sendRequest<Decoration[]>(
'm/decorationsRequest',
params
);
Server.highlighter.setHighlights(editor, decorations);
}

View file

@ -1,11 +1,18 @@
import * as vscode from 'vscode';
import { syntaxTreeUri, TextDocumentContentProvider } from '../commands/syntaxTree';
import {
syntaxTreeUri,
TextDocumentContentProvider
} from '../commands/syntaxTree';
export function createHandler(textDocumentContentProvider: TextDocumentContentProvider) {
export function createHandler(
textDocumentContentProvider: TextDocumentContentProvider
) {
return (event: vscode.TextDocumentChangeEvent) => {
const doc = event.document;
if (doc.languageId !== 'rust') { return; }
if (doc.languageId !== 'rust') {
return;
}
afterLs(() => {
textDocumentContentProvider.eventEmitter.fire(syntaxTreeUri);
});

View file

@ -1,7 +1,4 @@
import * as changeActiveTextEditor from './change_active_text_editor';
import * as changeTextDocument from './change_text_document';
export {
changeActiveTextEditor,
changeTextDocument,
};
export { changeActiveTextEditor, changeTextDocument };

View file

@ -23,26 +23,34 @@ export function activate(context: vscode.ExtensionContext) {
registerCommand('ra-lsp.joinLines', commands.joinLines.handle);
registerCommand('ra-lsp.parentModule', commands.parentModule.handle);
registerCommand('ra-lsp.run', commands.runnables.handle);
registerCommand('ra-lsp.applySourceChange', commands.applySourceChange.handle);
registerCommand(
'ra-lsp.applySourceChange',
commands.applySourceChange.handle
);
// Notifications are events triggered by the language server
const allNotifications: Iterable<[string, lc.GenericNotificationHandler]> = [
['m/publishDecorations', notifications.publishDecorations.handle],
];
const allNotifications: Iterable<
[string, lc.GenericNotificationHandler]
> = [['m/publishDecorations', notifications.publishDecorations.handle]];
// The events below are plain old javascript events, triggered and handled by vscode
vscode.window.onDidChangeActiveTextEditor(events.changeActiveTextEditor.handle);
vscode.window.onDidChangeActiveTextEditor(
events.changeActiveTextEditor.handle
);
const textDocumentContentProvider = new TextDocumentContentProvider();
disposeOnDeactivation(vscode.workspace.registerTextDocumentContentProvider(
'ra-lsp',
textDocumentContentProvider,
));
disposeOnDeactivation(
vscode.workspace.registerTextDocumentContentProvider(
'ra-lsp',
textDocumentContentProvider
)
);
vscode.workspace.onDidChangeTextDocument(
events.changeTextDocument.createHandler(textDocumentContentProvider),
null,
context.subscriptions);
context.subscriptions
);
// Start the language server, finally!
Server.start(allNotifications);

View file

@ -9,15 +9,24 @@ export interface Decoration {
}
export class Highlighter {
private static initDecorations(): Map<string, vscode.TextEditorDecorationType> {
const decor = (color: string) => vscode.window.createTextEditorDecorationType({ color });
private static initDecorations(): Map<
string,
vscode.TextEditorDecorationType
> {
const decor = (color: string) =>
vscode.window.createTextEditorDecorationType({ color });
const decorations: Iterable<[string, vscode.TextEditorDecorationType]> = [
const decorations: Iterable<
[string, vscode.TextEditorDecorationType]
> = [
['background', decor('#3F3F3F')],
['error', vscode.window.createTextEditorDecorationType({
borderColor: 'red',
borderStyle: 'none none dashed none',
})],
[
'error',
vscode.window.createTextEditorDecorationType({
borderColor: 'red',
borderStyle: 'none none dashed none'
})
],
['comment', decor('#7F9F7F')],
['string', decor('#CC9393')],
['keyword', decor('#F0DFAF')],
@ -26,13 +35,16 @@ export class Highlighter {
['builtin', decor('#DD6718')],
['text', decor('#DCDCCC')],
['attribute', decor('#BFEBBF')],
['literal', decor('#DFAF8F')],
['literal', decor('#DFAF8F')]
];
return new Map<string, vscode.TextEditorDecorationType>(decorations);
}
private decorations: (Map<string, vscode.TextEditorDecorationType> | null) = null;
private decorations: Map<
string,
vscode.TextEditorDecorationType
> | null = null;
public removeHighlights() {
if (this.decorations == null) {
@ -47,10 +59,7 @@ export class Highlighter {
this.decorations = null;
}
public setHighlights(
editor: vscode.TextEditor,
highlights: Decoration[],
) {
public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) {
// Initialize decorations if necessary
//
// Note: decoration objects need to be kept around so we can dispose them
@ -68,13 +77,15 @@ export class Highlighter {
if (!byTag.get(d.tag)) {
continue;
}
byTag.get(d.tag)!.push(
Server.client.protocol2CodeConverter.asRange(d.range),
);
byTag
.get(d.tag)!
.push(Server.client.protocol2CodeConverter.asRange(d.range));
}
for (const tag of byTag.keys()) {
const dec = this.decorations.get(tag) as vscode.TextEditorDecorationType;
const dec = this.decorations.get(
tag
) as vscode.TextEditorDecorationType;
const ranges = byTag.get(tag)!;
editor.setDecorations(dec, ranges);
}

View file

@ -1,5 +1,3 @@
import * as publishDecorations from './publish_decorations';
export {
publishDecorations,
};
export { publishDecorations };

View file

@ -10,11 +10,10 @@ export interface PublishDecorationsParams {
export function handle(params: PublishDecorationsParams) {
const targetEditor = vscode.window.visibleTextEditors.find(
(editor) => editor.document.uri.toString() === params.uri,
);
if (!Server.config.highlightingOn || !targetEditor) { return; }
Server.highlighter.setHighlights(
targetEditor,
params.decorations,
editor => editor.document.uri.toString() === params.uri
);
if (!Server.config.highlightingOn || !targetEditor) {
return;
}
Server.highlighter.setHighlights(targetEditor, params.decorations);
}

View file

@ -8,24 +8,26 @@ export class Server {
public static config = new Config();
public static client: lc.LanguageClient;
public static start(notificationHandlers: Iterable<[string, lc.GenericNotificationHandler]>) {
public static start(
notificationHandlers: Iterable<[string, lc.GenericNotificationHandler]>
) {
const run: lc.Executable = {
command: 'ra_lsp_server',
options: { cwd: '.' },
options: { cwd: '.' }
};
const serverOptions: lc.ServerOptions = {
run,
debug: run,
debug: run
};
const clientOptions: lc.LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'rust' }],
documentSelector: [{ scheme: 'file', language: 'rust' }]
};
Server.client = new lc.LanguageClient(
'ra-lsp',
'rust-analyzer languge server',
serverOptions,
clientOptions,
clientOptions
);
Server.client.onReady().then(() => {
for (const [type, handler] of notificationHandlers) {

View file

@ -3,15 +3,10 @@
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es6"
],
"lib": ["es6"],
"sourceMap": true,
"rootDir": "src",
"strict": true
},
"exclude": [
"node_modules",
".vscode-test"
]
"exclude": ["node_modules", ".vscode-test"]
}

View file

@ -1,13 +1,9 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"extends": ["tslint:recommended", "tslint-config-prettier"],
"rules": {
"quotemark": [true, "single"],
"interface-name": false,
"object-literal-sort-keys": false
},
"rulesDirectory": []
}
}