about summary refs log tree commit diff
path: root/editors/code/src
diff options
context:
space:
mode:
authorAdolfo OchagavĂ­a <aochagavia92@gmail.com>2018-10-07 22:44:25 +0200
committerAdolfo OchagavĂ­a <aochagavia92@gmail.com>2018-10-07 23:03:38 +0200
commit69de7e2fd71c3a808f0ac856d7b105eeb210f169 (patch)
tree62a163c43cb710cff18de6c7e8e47a81038ad1bb /editors/code/src
parente4fdfd15012c983e4555996aa466b57d787e4385 (diff)
downloadrust-69de7e2fd71c3a808f0ac856d7b105eeb210f169.tar.gz
rust-69de7e2fd71c3a808f0ac856d7b105eeb210f169.zip
Refactor vscode extension
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/commands.ts17
-rw-r--r--editors/code/src/commands/apply_source_change.ts58
-rw-r--r--editors/code/src/commands/extend_selection.ts29
-rw-r--r--editors/code/src/commands/join_lines.ts21
-rw-r--r--editors/code/src/commands/matching_brace.ts27
-rw-r--r--editors/code/src/commands/parent_module.ts22
-rw-r--r--editors/code/src/commands/runnables.ts88
-rw-r--r--editors/code/src/commands/syntaxTree.ts38
-rw-r--r--editors/code/src/events.ts7
-rw-r--r--editors/code/src/events/change_active_text_editor.ts14
-rw-r--r--editors/code/src/events/change_text_document.ts19
-rw-r--r--editors/code/src/extension.ts441
-rw-r--r--editors/code/src/highlighting.ts78
-rw-r--r--editors/code/src/server.ts74
14 files changed, 518 insertions, 415 deletions
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
new file mode 100644
index 00000000000..99cac3379e4
--- /dev/null
+++ b/editors/code/src/commands.ts
@@ -0,0 +1,17 @@
+import * as applySourceChange from './commands/apply_source_change';
+import * as extendSelection from './commands/extend_selection';
+import * as joinLines from './commands/join_lines';
+import * as matchingBrace from './commands/matching_brace';
+import * as parentModule from './commands/parent_module';
+import * as runnables from './commands/runnables';
+import * as syntaxTree from './commands/syntaxTree';
+
+export {
+    applySourceChange,
+    extendSelection,
+    joinLines,
+    matchingBrace,
+    parentModule,
+    runnables,
+    syntaxTree
+}
diff --git a/editors/code/src/commands/apply_source_change.ts b/editors/code/src/commands/apply_source_change.ts
new file mode 100644
index 00000000000..dcbbb2b0984
--- /dev/null
+++ b/editors/code/src/commands/apply_source_change.ts
@@ -0,0 +1,58 @@
+import * as vscode from 'vscode';
+import * as lc from 'vscode-languageclient'
+
+import { Server } from '../server';
+
+interface FileSystemEdit {
+    type: string;
+    uri?: string;
+    src?: string;
+    dst?: string;
+}
+
+export interface SourceChange {
+    label: string,
+    sourceFileEdits: lc.TextDocumentEdit[],
+    fileSystemEdits: FileSystemEdit[],
+    cursorPosition?: lc.TextDocumentPositionParams,
+}
+
+export async function handle(change: SourceChange) {
+    console.log(`applySOurceChange ${JSON.stringify(change)}`)
+    let wsEdit = new vscode.WorkspaceEdit()
+    for (let sourceEdit of change.sourceFileEdits) {
+        let uri = Server.client.protocol2CodeConverter.asUri(sourceEdit.textDocument.uri)
+        let edits = Server.client.protocol2CodeConverter.asTextEdits(sourceEdit.edits)
+        wsEdit.set(uri, edits)
+    }
+    let created;
+    let moved;
+    for (let fsEdit of change.fileSystemEdits) {
+        if (fsEdit.type == "createFile") {
+            let uri = vscode.Uri.parse(fsEdit.uri!)
+            wsEdit.createFile(uri)
+            created = uri
+        } else if (fsEdit.type == "moveFile") {
+            let src = vscode.Uri.parse(fsEdit.src!)
+            let dst = vscode.Uri.parse(fsEdit.dst!)
+            wsEdit.renameFile(src, dst)
+            moved = dst
+        } else {
+            console.error(`unknown op: ${JSON.stringify(fsEdit)}`)
+        }
+    }
+    let toOpen = created || moved
+    let toReveal = change.cursorPosition
+    await vscode.workspace.applyEdit(wsEdit)
+    if (toOpen) {
+        let doc = await vscode.workspace.openTextDocument(toOpen)
+        await vscode.window.showTextDocument(doc)
+    } else if (toReveal) {
+        let uri = Server.client.protocol2CodeConverter.asUri(toReveal.textDocument.uri)
+        let position = Server.client.protocol2CodeConverter.asPosition(toReveal.position)
+        let editor = vscode.window.activeTextEditor;
+        if (!editor || editor.document.uri.toString() != uri.toString()) return
+        if (!editor.selection.isEmpty) return
+        editor!.selection = new vscode.Selection(position, position)
+    }
+}
diff --git a/editors/code/src/commands/extend_selection.ts b/editors/code/src/commands/extend_selection.ts
new file mode 100644
index 00000000000..b90828ba9ba
--- /dev/null
+++ b/editors/code/src/commands/extend_selection.ts
@@ -0,0 +1,29 @@
+import * as vscode from 'vscode';
+
+import { TextDocumentIdentifier, Range } from "vscode-languageclient";
+import { Server } from '../server';
+
+interface ExtendSelectionParams {
+    textDocument: TextDocumentIdentifier;
+    selections: Range[];
+}
+
+interface ExtendSelectionResult {
+    selections: Range[];
+}
+
+export async function handle() {
+    let editor = vscode.window.activeTextEditor
+    if (editor == null || editor.document.languageId != "rust") return
+    let request: ExtendSelectionParams = {
+        textDocument: { uri: editor.document.uri.toString() },
+        selections: editor.selections.map((s) => {
+            return Server.client.code2ProtocolConverter.asRange(s)
+        })
+    }
+    let response = await Server.client.sendRequest<ExtendSelectionResult>("m/extendSelection", request)
+    editor.selections = response.selections.map((range: Range) => {
+        let r = Server.client.protocol2CodeConverter.asRange(range)
+        return new vscode.Selection(r.start, r.end)
+    })
+}
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
new file mode 100644
index 00000000000..7ae7b9d7601
--- /dev/null
+++ b/editors/code/src/commands/join_lines.ts
@@ -0,0 +1,21 @@
+import * as vscode from 'vscode';
+
+import { TextDocumentIdentifier, Range } from "vscode-languageclient";
+import { Server } from '../server';
+import { handle as applySourceChange, SourceChange } from './apply_source_change';
+
+interface JoinLinesParams {
+    textDocument: TextDocumentIdentifier;
+    range: Range;
+}
+
+export async function handle() {
+    let editor = vscode.window.activeTextEditor
+    if (editor == null || editor.document.languageId != "rust") return
+    let request: JoinLinesParams = {
+        textDocument: { uri: editor.document.uri.toString() },
+        range: Server.client.code2ProtocolConverter.asRange(editor.selection),
+    }
+    let change = await Server.client.sendRequest<SourceChange>("m/joinLines", request)
+    await applySourceChange(change)
+}
diff --git a/editors/code/src/commands/matching_brace.ts b/editors/code/src/commands/matching_brace.ts
new file mode 100644
index 00000000000..572c15ce830
--- /dev/null
+++ b/editors/code/src/commands/matching_brace.ts
@@ -0,0 +1,27 @@
+import * as vscode from 'vscode';
+
+import { TextDocumentIdentifier, Position } from "vscode-languageclient";
+import { Server } from '../server';
+
+interface FindMatchingBraceParams {
+    textDocument: TextDocumentIdentifier;
+    offsets: Position[];
+}
+
+export async function handle() {
+    let editor = vscode.window.activeTextEditor
+    if (editor == null || editor.document.languageId != "rust") return
+    let request: FindMatchingBraceParams = {
+        textDocument: { uri: editor.document.uri.toString() },
+        offsets: editor.selections.map((s) => {
+            return Server.client.code2ProtocolConverter.asPosition(s.active)
+        })
+    }
+    let response = await Server.client.sendRequest<Position[]>("m/findMatchingBrace", request)
+    editor.selections = editor.selections.map((sel, idx) => {
+        let active = Server.client.protocol2CodeConverter.asPosition(response[idx])
+        let anchor = sel.isEmpty ? active : sel.anchor
+        return new vscode.Selection(anchor, active)
+    })
+    editor.revealRange(editor.selection)
+};
diff --git a/editors/code/src/commands/parent_module.ts b/editors/code/src/commands/parent_module.ts
new file mode 100644
index 00000000000..dae60bfb4a3
--- /dev/null
+++ b/editors/code/src/commands/parent_module.ts
@@ -0,0 +1,22 @@
+import * as vscode from 'vscode';
+
+import { TextDocumentIdentifier, Location } from "vscode-languageclient";
+import { Server } from '../server';
+
+export async function handle() {
+    let editor = vscode.window.activeTextEditor
+    if (editor == null || editor.document.languageId != "rust") return
+    let request: TextDocumentIdentifier = {
+        uri: editor.document.uri.toString()
+    }
+    let response = await Server.client.sendRequest<Location[]>("m/parentModule", request)
+    let loc = response[0]
+    if (loc == null) return
+    let uri = Server.client.protocol2CodeConverter.asUri(loc.uri)
+    let range = Server.client.protocol2CodeConverter.asRange(loc.range)
+
+    let doc = await vscode.workspace.openTextDocument(uri)
+    let e = await vscode.window.showTextDocument(doc)
+    e.selection = new vscode.Selection(range.start, range.start)
+    e.revealRange(range, vscode.TextEditorRevealType.InCenter)
+}
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
new file mode 100644
index 00000000000..45c16497dbe
--- /dev/null
+++ b/editors/code/src/commands/runnables.ts
@@ -0,0 +1,88 @@
+import * as vscode from 'vscode';
+import * as lc from 'vscode-languageclient'
+import { Server } from '../server';
+
+interface RunnablesParams {
+    textDocument: lc.TextDocumentIdentifier,
+    position?: lc.Position,
+}
+
+interface Runnable {
+    range: lc.Range;
+    label: string;
+    bin: string;
+    args: string[];
+    env: { [index: string]: string },
+}
+
+class RunnableQuickPick implements vscode.QuickPickItem {
+    label: string;
+    description?: string | undefined;
+    detail?: string | undefined;
+    picked?: boolean | undefined;
+
+    constructor(public runnable: Runnable) {
+        this.label = runnable.label
+    }
+}
+
+interface CargoTaskDefinition extends vscode.TaskDefinition {
+    type: 'cargo';
+    label: string;
+    command: string;
+    args: Array<string>;
+    env?: { [key: string]: string };
+}
+
+function createTask(spec: Runnable): vscode.Task {
+    const TASK_SOURCE = 'Rust';
+    let definition: CargoTaskDefinition = {
+        type: 'cargo',
+        label: 'cargo',
+        command: spec.bin,
+        args: spec.args,
+        env: spec.env
+    }
+
+    let execCmd = `${definition.command} ${definition.args.join(' ')}`;
+    let execOption: vscode.ShellExecutionOptions = {
+        cwd: '.',
+        env: definition.env,
+    };
+    let exec = new vscode.ShellExecution(`clear; ${execCmd}`, execOption);
+
+    let f = vscode.workspace.workspaceFolders![0]
+    let t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
+    return t;
+}
+
+let prevRunnable: RunnableQuickPick | undefined = undefined
+export async function handle() {
+    let editor = vscode.window.activeTextEditor
+    if (editor == null || editor.document.languageId != "rust") return
+    let textDocument: lc.TextDocumentIdentifier = {
+        uri: editor.document.uri.toString()
+    }
+    let params: RunnablesParams = {
+        textDocument,
+        position: Server.client.code2ProtocolConverter.asPosition(editor.selection.active)
+    }
+    let runnables = await Server.client.sendRequest<Runnable[]>('m/runnables', params)
+    let items: RunnableQuickPick[] = []
+    if (prevRunnable) {
+        items.push(prevRunnable)
+    }
+    for (let r of runnables) {
+        if (prevRunnable && JSON.stringify(prevRunnable.runnable) == JSON.stringify(r)) {
+            continue
+        }
+        items.push(new RunnableQuickPick(r))
+    }
+    let item = await vscode.window.showQuickPick(items)
+    if (item) {
+        item.detail = "rerun"
+        prevRunnable = item
+        let task = createTask(item.runnable)
+        return await vscode.tasks.executeTask(task)
+    }
+}
diff --git a/editors/code/src/commands/syntaxTree.ts b/editors/code/src/commands/syntaxTree.ts
new file mode 100644
index 00000000000..d5daa9302c6
--- /dev/null
+++ b/editors/code/src/commands/syntaxTree.ts
@@ -0,0 +1,38 @@
+import * as vscode from 'vscode';
+import { TextDocumentIdentifier } from 'vscode-languageclient';
+
+import { Server } from '../server';
+
+export const syntaxTreeUri = vscode.Uri.parse('ra-lsp://syntaxtree');
+
+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> {
+        let editor = vscode.window.activeTextEditor;
+        if (editor == null) return ""
+        let request: SyntaxTreeParams = {
+            textDocument: { uri: editor.document.uri.toString() }
+        };
+        return Server.client.sendRequest<SyntaxTreeResult>("m/syntaxTree", request);
+    }
+
+    get onDidChange(): vscode.Event<vscode.Uri> {
+        return this.eventEmitter.event
+    }
+}
+
+interface SyntaxTreeParams {
+    textDocument: TextDocumentIdentifier;
+}
+
+type SyntaxTreeResult = string;
+
+// Opens the virtual file that will show the syntax tree
+//
+// The contents of the file come from the `TextDocumentContentProvider`
+export async function handle() {
+    let document = await vscode.workspace.openTextDocument(syntaxTreeUri)
+    return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true)
+}
diff --git a/editors/code/src/events.ts b/editors/code/src/events.ts
new file mode 100644
index 00000000000..b143bb2565c
--- /dev/null
+++ b/editors/code/src/events.ts
@@ -0,0 +1,7 @@
+import * as changeActiveTextEditor from './events/change_active_text_editor'
+import * as changeTextDocument from './events/change_text_document';
+
+export {
+    changeActiveTextEditor,
+    changeTextDocument
+}
\ No newline at end of file
diff --git a/editors/code/src/events/change_active_text_editor.ts b/editors/code/src/events/change_active_text_editor.ts
new file mode 100644
index 00000000000..bbdd5309869
--- /dev/null
+++ b/editors/code/src/events/change_active_text_editor.ts
@@ -0,0 +1,14 @@
+import { TextEditor } from "vscode";
+import { TextDocumentIdentifier } from "vscode-languageclient";
+
+import { Server } from "../server";
+import { Decoration } from "../highlighting";
+
+export async function handle(editor: TextEditor | undefined) {
+    if (!Server.config.highlightingOn || !editor || editor.document.languageId != 'rust') return
+    let params: TextDocumentIdentifier = {
+        uri: editor.document.uri.toString()
+    }
+    let decorations = await Server.client.sendRequest<Decoration[]>("m/decorationsRequest", params)
+    Server.highlighter.setHighlights(editor, decorations)
+}
\ No newline at end of file
diff --git a/editors/code/src/events/change_text_document.ts b/editors/code/src/events/change_text_document.ts
new file mode 100644
index 00000000000..83ee6c9ee99
--- /dev/null
+++ b/editors/code/src/events/change_text_document.ts
@@ -0,0 +1,19 @@
+import * as vscode from 'vscode';
+
+import { syntaxTreeUri, TextDocumentContentProvider } from '../commands/syntaxTree';
+
+export function createHandler(textDocumentContentProvider: TextDocumentContentProvider) {
+    return (event: vscode.TextDocumentChangeEvent) => {
+        let doc = event.document
+        if (doc.languageId != "rust") return
+        afterLs(() => {
+            textDocumentContentProvider.eventEmitter.fire(syntaxTreeUri);
+        })
+    }
+}
+
+// We need to order this after LS updates, but there's no API for that.
+// Hence, good old setTimeout.
+function afterLs(f: () => any) {
+    setTimeout(f, 10)
+}
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index fde6a480d85..595fb98fe3f 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -1,434 +1,45 @@
-'use strict';
 import * as vscode from 'vscode';
-import * as lc from 'vscode-languageclient'
 
-let client: lc.LanguageClient;
-
-let uris = {
-    syntaxTree: vscode.Uri.parse('ra-lsp://syntaxtree')
-}
-
-let highlightingOn = true;
+import * as commands from './commands'
+import * as events from './events'
+import { Server } from './server';
+import { TextDocumentContentProvider } from './commands/syntaxTree';
 
 export function activate(context: vscode.ExtensionContext) {
-    let applyHighlightingOn = () => {
-        let config = vscode.workspace.getConfiguration('ra-lsp');
-        if (config.has('highlightingOn')) {
-            highlightingOn = config.get('highlightingOn') as boolean;
-        };
-
-        if (!highlightingOn) {
-            removeHighlights();
-        }
-    };
-
-    // Apply the highlightingOn config now and whenever the config changes
-    applyHighlightingOn();
-    vscode.workspace.onDidChangeConfiguration(_ => {
-        applyHighlightingOn();
-    });
-
-    let textDocumentContentProvider = new TextDocumentContentProvider()
-    let dispose = (disposable: vscode.Disposable) => {
+    function disposeOnDeactivation(disposable: vscode.Disposable) {
         context.subscriptions.push(disposable);
     }
-    let registerCommand = (name: string, f: any) => {
-        dispose(vscode.commands.registerCommand(name, f))
-    }
-
-    registerCommand('ra-lsp.syntaxTree', () => openDoc(uris.syntaxTree))
-    registerCommand('ra-lsp.extendSelection', async () => {
-        let editor = vscode.window.activeTextEditor
-        if (editor == null || editor.document.languageId != "rust") return
-        let request: ExtendSelectionParams = {
-            textDocument: { uri: editor.document.uri.toString() },
-            selections: editor.selections.map((s) => {
-                return client.code2ProtocolConverter.asRange(s)
-            })
-        }
-        let response = await client.sendRequest<ExtendSelectionResult>("m/extendSelection", request)
-        editor.selections = response.selections.map((range) => {
-            let r = client.protocol2CodeConverter.asRange(range)
-            return new vscode.Selection(r.start, r.end)
-        })
-    })
-    registerCommand('ra-lsp.matchingBrace', async () => {
-        let editor = vscode.window.activeTextEditor
-        if (editor == null || editor.document.languageId != "rust") return
-        let request: FindMatchingBraceParams = {
-            textDocument: { uri: editor.document.uri.toString() },
-            offsets: editor.selections.map((s) => {
-                return client.code2ProtocolConverter.asPosition(s.active)
-            })
-        }
-        let response = await client.sendRequest<lc.Position[]>("m/findMatchingBrace", request)
-        editor.selections = editor.selections.map((sel, idx) => {
-            let active = client.protocol2CodeConverter.asPosition(response[idx])
-            let anchor = sel.isEmpty ? active : sel.anchor
-            return new vscode.Selection(anchor, active)
-        })
-        editor.revealRange(editor.selection)
-    })
-    registerCommand('ra-lsp.joinLines', async () => {
-        let editor = vscode.window.activeTextEditor
-        if (editor == null || editor.document.languageId != "rust") return
-        let request: JoinLinesParams = {
-            textDocument: { uri: editor.document.uri.toString() },
-            range: client.code2ProtocolConverter.asRange(editor.selection),
-        }
-        let change = await client.sendRequest<SourceChange>("m/joinLines", request)
-        await applySourceChange(change)
-    })
-    registerCommand('ra-lsp.parentModule', async () => {
-        let editor = vscode.window.activeTextEditor
-        if (editor == null || editor.document.languageId != "rust") return
-        let request: lc.TextDocumentIdentifier = {
-            uri: editor.document.uri.toString()
-        }
-        let response = await client.sendRequest<lc.Location[]>("m/parentModule", request)
-        let loc = response[0]
-        if (loc == null) return
-        let uri = client.protocol2CodeConverter.asUri(loc.uri)
-        let range = client.protocol2CodeConverter.asRange(loc.range)
 
-        let doc = await vscode.workspace.openTextDocument(uri)
-        let e = await vscode.window.showTextDocument(doc)
-        e.selection = new vscode.Selection(range.start, range.start)
-        e.revealRange(range, vscode.TextEditorRevealType.InCenter)
-    })
+    function registerCommand(name: string, f: any) {
+        disposeOnDeactivation(vscode.commands.registerCommand(name, f))
+    }
 
-    let prevRunnable: RunnableQuickPick | undefined = undefined
-    registerCommand('ra-lsp.run', async () => {
-        let editor = vscode.window.activeTextEditor
-        if (editor == null || editor.document.languageId != "rust") return
-        let textDocument: lc.TextDocumentIdentifier = {
-            uri: editor.document.uri.toString()
-        }
-        let params: RunnablesParams = {
-            textDocument,
-            position: client.code2ProtocolConverter.asPosition(editor.selection.active)
-        }
-        let runnables = await client.sendRequest<Runnable[]>('m/runnables', params)
-        let items: RunnableQuickPick[] = []
-        if (prevRunnable) {
-            items.push(prevRunnable)
-        }
-        for (let r of runnables) {
-            if (prevRunnable && JSON.stringify(prevRunnable.runnable) == JSON.stringify(r)) {
-                continue
-            }
-            items.push(new RunnableQuickPick(r))
-        }
-        let item = await vscode.window.showQuickPick(items)
-        if (item) {
-            item.detail = "rerun"
-            prevRunnable = item
-            let task = createTask(item.runnable)
-            return await vscode.tasks.executeTask(task)
-        }
-    })
-    registerCommand('ra-lsp.applySourceChange', applySourceChange)
+    registerCommand('ra-lsp.syntaxTree', commands.syntaxTree.handle)
+    registerCommand('ra-lsp.extendSelection', commands.extendSelection.handle);
+    registerCommand('ra-lsp.matchingBrace', commands.matchingBrace.handle);
+    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);
 
-    dispose(vscode.workspace.registerTextDocumentContentProvider(
+    let textDocumentContentProvider = new TextDocumentContentProvider()
+    disposeOnDeactivation(vscode.workspace.registerTextDocumentContentProvider(
         'ra-lsp',
         textDocumentContentProvider
     ))
-    startServer()
-    vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => {
-        let doc = event.document
-        if (doc.languageId != "rust") return
-        afterLs(() => {
-            textDocumentContentProvider.eventEmitter.fire(uris.syntaxTree)
-        })
-    }, null, context.subscriptions)
-    vscode.window.onDidChangeActiveTextEditor(async (editor) => {
-        if (!highlightingOn || !editor || editor.document.languageId != 'rust') return
-        let params: lc.TextDocumentIdentifier = {
-            uri: editor.document.uri.toString()
-        }
-        let decorations = await client.sendRequest<Decoration[]>("m/decorationsRequest", params)
-        setHighlights(editor, decorations)
-    })
-}
 
-// We need to order this after LS updates, but there's no API for that.
-// Hence, good old setTimeout.
-function afterLs(f: () => any) {
-    setTimeout(f, 10)
+    Server.start()
+
+    vscode.workspace.onDidChangeTextDocument(
+        events.changeTextDocument.createHandler(textDocumentContentProvider),
+        null,
+        context.subscriptions)
+    vscode.window.onDidChangeActiveTextEditor(events.changeActiveTextEditor.handle)
 }
 
 export function deactivate(): Thenable<void> {
-    if (!client) {
+    if (!Server.client) {
         return Promise.resolve();
     }
-    return client.stop();
-}
-
-function startServer() {
-    let run: lc.Executable = {
-        command: "ra_lsp_server",
-        options: { cwd: "." }
-    }
-    let serverOptions: lc.ServerOptions = {
-        run,
-        debug: run
-    };
-
-    let clientOptions: lc.LanguageClientOptions = {
-        documentSelector: [{ scheme: 'file', language: 'rust' }],
-    };
-
-    client = new lc.LanguageClient(
-        'ra-lsp',
-        'rust-analyzer languge server',
-        serverOptions,
-        clientOptions,
-    );
-    client.onReady().then(() => {
-        client.onNotification(
-            "m/publishDecorations",
-            (params: PublishDecorationsParams) => {
-                let editor = vscode.window.visibleTextEditors.find(
-                    (editor) => editor.document.uri.toString() == params.uri
-                )
-                if (!highlightingOn || !editor) return;
-                setHighlights(
-                    editor,
-                    params.decorations,
-                )
-            }
-        )
-    })
-    client.start();
-}
-
-async function openDoc(uri: vscode.Uri) {
-    let document = await vscode.workspace.openTextDocument(uri)
-    return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true)
-}
-
-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> {
-        let editor = vscode.window.activeTextEditor;
-        if (editor == null) return ""
-        let request: SyntaxTreeParams = {
-            textDocument: { uri: editor.document.uri.toString() }
-        };
-        return client.sendRequest<SyntaxTreeResult>("m/syntaxTree", request);
-    }
-
-    get onDidChange(): vscode.Event<vscode.Uri> {
-        return this.eventEmitter.event
-    }
-}
-
-let decorations: { [index: string]: vscode.TextEditorDecorationType } = {};
-
-function initDecorations() {
-    const decor = (obj: any) => vscode.window.createTextEditorDecorationType({ color: obj })
-    decorations = {
-        background: decor("#3F3F3F"),
-        error: vscode.window.createTextEditorDecorationType({
-            borderColor: "red",
-            borderStyle: "none none dashed none",
-        }),
-        comment: decor("#7F9F7F"),
-        string: decor("#CC9393"),
-        keyword: decor("#F0DFAF"),
-        function: decor("#93E0E3"),
-        parameter: decor("#94BFF3"),
-        builtin: decor("#DD6718"),
-        text: decor("#DCDCCC"),
-        attribute: decor("#BFEBBF"),
-        literal: decor("#DFAF8F"),
-    }
-}
-
-function removeHighlights() {
-    for (let tag in decorations) {
-        decorations[tag].dispose();
-    }
-
-    decorations = {};
-}
-
-function setHighlights(
-    editor: vscode.TextEditor,
-    highlights: Array<Decoration>
-) {
-    // Initialize decorations if necessary
-    //
-    // Note: decoration objects need to be kept around so we can dispose them
-    // if the user disables syntax highlighting
-    if (Object.keys(decorations).length === 0) {
-        initDecorations();
-    }
-
-    let byTag: Map<string, vscode.Range[]> = new Map()
-    for (let tag in decorations) {
-        byTag.set(tag, [])
-    }
-
-    for (let d of highlights) {
-        if (!byTag.get(d.tag)) {
-            console.log(`unknown tag ${d.tag}`)
-            continue
-        }
-        byTag.get(d.tag)!.push(
-            client.protocol2CodeConverter.asRange(d.range)
-        )
-    }
-
-    for (let tag of byTag.keys()) {
-        let dec: vscode.TextEditorDecorationType = decorations[tag]
-        let ranges = byTag.get(tag)!
-        editor.setDecorations(dec, ranges)
-    }
-}
-
-interface SyntaxTreeParams {
-    textDocument: lc.TextDocumentIdentifier;
-}
-
-type SyntaxTreeResult = string
-
-interface ExtendSelectionParams {
-    textDocument: lc.TextDocumentIdentifier;
-    selections: lc.Range[];
-}
-
-interface ExtendSelectionResult {
-    selections: lc.Range[];
-}
-
-interface FindMatchingBraceParams {
-    textDocument: lc.TextDocumentIdentifier;
-    offsets: lc.Position[];
-}
-
-interface JoinLinesParams {
-    textDocument: lc.TextDocumentIdentifier;
-    range: lc.Range;
-}
-
-interface PublishDecorationsParams {
-    uri: string,
-    decorations: Decoration[],
-}
-
-interface RunnablesParams {
-    textDocument: lc.TextDocumentIdentifier,
-    position?: lc.Position,
-}
-
-interface Runnable {
-    range: lc.Range;
-    label: string;
-    bin: string;
-    args: string[];
-    env: { [index: string]: string },
-}
-
-class RunnableQuickPick implements vscode.QuickPickItem {
-    label: string;
-    description?: string | undefined;
-    detail?: string | undefined;
-    picked?: boolean | undefined;
-
-    constructor(public runnable: Runnable) {
-        this.label = runnable.label
-    }
-}
-
-interface Decoration {
-    range: lc.Range,
-    tag: string,
-}
-
-
-interface CargoTaskDefinition extends vscode.TaskDefinition {
-    type: 'cargo';
-    label: string;
-    command: string;
-    args: Array<string>;
-    env?: { [key: string]: string };
-}
-
-function createTask(spec: Runnable): vscode.Task {
-    const TASK_SOURCE = 'Rust';
-    let definition: CargoTaskDefinition = {
-        type: 'cargo',
-        label: 'cargo',
-        command: spec.bin,
-        args: spec.args,
-        env: spec.env
-    }
-
-    let execCmd = `${definition.command} ${definition.args.join(' ')}`;
-    let execOption: vscode.ShellExecutionOptions = {
-        cwd: '.',
-        env: definition.env,
-    };
-    let exec = new vscode.ShellExecution(`clear; ${execCmd}`, execOption);
-
-    let f = vscode.workspace.workspaceFolders![0]
-    let t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
-    return t;
-}
-
-interface FileSystemEdit {
-    type: string;
-    uri?: string;
-    src?: string;
-    dst?: string;
-}
-
-interface SourceChange {
-    label: string,
-    sourceFileEdits: lc.TextDocumentEdit[],
-    fileSystemEdits: FileSystemEdit[],
-    cursorPosition?: lc.TextDocumentPositionParams,
-}
-
-async function applySourceChange(change: SourceChange) {
-    console.log(`applySOurceChange ${JSON.stringify(change)}`)
-    let wsEdit = new vscode.WorkspaceEdit()
-    for (let sourceEdit of change.sourceFileEdits) {
-        let uri = client.protocol2CodeConverter.asUri(sourceEdit.textDocument.uri)
-        let edits = client.protocol2CodeConverter.asTextEdits(sourceEdit.edits)
-        wsEdit.set(uri, edits)
-    }
-    let created;
-    let moved;
-    for (let fsEdit of change.fileSystemEdits) {
-        if (fsEdit.type == "createFile") {
-            let uri = vscode.Uri.parse(fsEdit.uri!)
-            wsEdit.createFile(uri)
-            created = uri
-        } else if (fsEdit.type == "moveFile") {
-            let src = vscode.Uri.parse(fsEdit.src!)
-            let dst = vscode.Uri.parse(fsEdit.dst!)
-            wsEdit.renameFile(src, dst)
-            moved = dst
-        } else {
-            console.error(`unknown op: ${JSON.stringify(fsEdit)}`)
-        }
-    }
-    let toOpen = created || moved
-    let toReveal = change.cursorPosition
-    await vscode.workspace.applyEdit(wsEdit)
-    if (toOpen) {
-        let doc = await vscode.workspace.openTextDocument(toOpen)
-        await vscode.window.showTextDocument(doc)
-    } else if (toReveal) {
-        let uri = client.protocol2CodeConverter.asUri(toReveal.textDocument.uri)
-        let position = client.protocol2CodeConverter.asPosition(toReveal.position)
-        let editor = vscode.window.activeTextEditor;
-        if (!editor || editor.document.uri.toString() != uri.toString()) return
-        if (!editor.selection.isEmpty) return
-        editor!.selection = new vscode.Selection(position, position)
-    }
+    return Server.client.stop();
 }
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
new file mode 100644
index 00000000000..169ddb0df08
--- /dev/null
+++ b/editors/code/src/highlighting.ts
@@ -0,0 +1,78 @@
+import * as vscode from 'vscode';
+import * as lc from 'vscode-languageclient'
+
+import { Server } from './server';
+
+export interface Decoration {
+    range: lc.Range,
+    tag: string,
+}
+
+export class Highlighter {
+    private decorations: { [index: string]: vscode.TextEditorDecorationType };
+    constructor() {
+        this.decorations = {};
+    }
+
+    removeHighlights() {
+        for (let tag in this.decorations) {
+            this.decorations[tag].dispose();
+        }
+
+        this.decorations = {};
+    }
+
+    setHighlights(
+        editor: vscode.TextEditor,
+        highlights: Array<Decoration>
+    ) {
+        // Initialize decorations if necessary
+        //
+        // Note: decoration objects need to be kept around so we can dispose them
+        // if the user disables syntax highlighting
+        if (Object.keys(this.decorations).length === 0) {
+            this.initDecorations();
+        }
+
+        let byTag: Map<string, vscode.Range[]> = new Map()
+        for (let tag in this.decorations) {
+            byTag.set(tag, [])
+        }
+
+        for (let d of highlights) {
+            if (!byTag.get(d.tag)) {
+                console.log(`unknown tag ${d.tag}`)
+                continue
+            }
+            byTag.get(d.tag)!.push(
+                Server.client.protocol2CodeConverter.asRange(d.range)
+            )
+        }
+
+        for (let tag of byTag.keys()) {
+            let dec: vscode.TextEditorDecorationType = this.decorations[tag]
+            let ranges = byTag.get(tag)!
+            editor.setDecorations(dec, ranges)
+        }
+    }
+
+    private initDecorations() {
+        const decor = (obj: any) => vscode.window.createTextEditorDecorationType({ color: obj })
+        this.decorations = {
+            background: decor("#3F3F3F"),
+            error: vscode.window.createTextEditorDecorationType({
+                borderColor: "red",
+                borderStyle: "none none dashed none",
+            }),
+            comment: decor("#7F9F7F"),
+            string: decor("#CC9393"),
+            keyword: decor("#F0DFAF"),
+            function: decor("#93E0E3"),
+            parameter: decor("#94BFF3"),
+            builtin: decor("#DD6718"),
+            text: decor("#DCDCCC"),
+            attribute: decor("#BFEBBF"),
+            literal: decor("#DFAF8F"),
+        }
+    }
+}
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts
new file mode 100644
index 00000000000..c1c95e00842
--- /dev/null
+++ b/editors/code/src/server.ts
@@ -0,0 +1,74 @@
+import * as vscode from 'vscode';
+import * as lc from 'vscode-languageclient'
+
+import { Highlighter, Decoration } from './highlighting';
+
+export class Config {
+    highlightingOn = true;
+
+    constructor() {
+        vscode.workspace.onDidChangeConfiguration(_ => this.userConfigChanged());
+        this.userConfigChanged();
+    }
+
+    userConfigChanged() {
+        let config = vscode.workspace.getConfiguration('ra-lsp');
+        if (config.has('highlightingOn')) {
+            this.highlightingOn = config.get('highlightingOn') as boolean;
+        };
+
+        if (!this.highlightingOn) {
+            Server.highlighter.removeHighlights();
+        }
+    }
+}
+
+export class Server {
+    static highlighter = new Highlighter();
+    static config = new Config();
+    static client: lc.LanguageClient;
+
+
+    static start() {
+        let run: lc.Executable = {
+            command: "ra_lsp_server",
+            options: { cwd: "." }
+        }
+        let serverOptions: lc.ServerOptions = {
+            run,
+            debug: run
+        };
+
+        let clientOptions: lc.LanguageClientOptions = {
+            documentSelector: [{ scheme: 'file', language: 'rust' }],
+        };
+
+        Server.client = new lc.LanguageClient(
+            'ra-lsp',
+            'rust-analyzer languge server',
+            serverOptions,
+            clientOptions,
+        );
+        Server.client.onReady().then(() => {
+            Server.client.onNotification(
+                "m/publishDecorations",
+                (params: PublishDecorationsParams) => {
+                    let editor = vscode.window.visibleTextEditors.find(
+                        (editor) => editor.document.uri.toString() == params.uri
+                    )
+                    if (!Server.config.highlightingOn || !editor) return;
+                    Server.highlighter.setHighlights(
+                        editor,
+                        params.decorations,
+                    )
+                }
+            )
+        })
+        Server.client.start();
+    }
+}
+
+interface PublishDecorationsParams {
+    uri: string,
+    decorations: Decoration[],
+}