Skip to content

Commit

Permalink
Try to keep track of the correct locations whilst editing files
Browse files Browse the repository at this point in the history
  • Loading branch information
mpickering committed Feb 23, 2019
1 parent 38414a7 commit c7791de
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 7 deletions.
153 changes: 149 additions & 4 deletions server/src/lsifDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface Vertices {
ranges: Map<Id, Range>;
}

type PositionUpdater = (position: lsp.Position) => lsp.Position | null;
type ItemTarget = { type: 'declaration'; range: Range } | { type: 'definition'; range: Range } | { type: 'reference'; range: Range } | { type: 'referenceResult'; result: ReferenceResult };

interface Out {
Expand Down Expand Up @@ -59,6 +60,12 @@ export class LsifDatabase {
private out: Out;
private in: In;

// Used to convert incoming positions into positions in the unedited document
private newToOld : PositionUpdater = function(s){return s;};
// Used to convert outgoing positions into positions in the edited document
private oldToNew : PositionUpdater = function(s){return s;};


constructor(private file: string) {
this.vertices = {
all: new Map(),
Expand Down Expand Up @@ -281,11 +288,16 @@ export class LsifDatabase {
if (Array.isArray(definitionResult.result)) {
let result: lsp.Location[] = [];
for (let element of definitionResult.result) {
result.push(this.asLocation(element));
this.pushIfValidLocation(result, this.asLocation(element));
}
return result;
} else {
return this.asLocation(definitionResult.result);
let res = this.oldLocToNewLoc(this.asLocation(definitionResult.result));
if (res == null){
return undefined;
} else {
return res;
}
}
}

Expand Down Expand Up @@ -321,6 +333,33 @@ export class LsifDatabase {
return this.asReferenceResult(referenceResult, context, new Set());
}

public updateLocations(uri : string, changes : lsp.TextDocumentContentChangeEvent[]) {
let new_update_forwards = function(p: lsp.Position | null){
return (changes.reduce(LsifDatabase.updatePosition("forward"), p))};

let new_update_backwards = function(p: lsp.Position | null){
return (changes.reduce(LsifDatabase.updatePosition("backwards"), p))};

let prev_newToOld = this.newToOld;

this.newToOld = function(p: lsp.Position){ let new_pos = new_update_forwards(p);
if (new_pos == null){
return null;
} else {
return prev_newToOld(new_pos);
}};

let prev_oldToNew = this.oldToNew;

this.oldToNew = function(p: lsp.Position){ let new_pos = prev_oldToNew(p);
if (new_pos == null){
return null;
} else {
return new_update_backwards(new_pos);
}};
return null;
}

private getResult<T>(range: Range, edges: Map<Id, T>): T | undefined {
let result: T | undefined = edges.get(range.id);
if (result !== undefined) {
Expand Down Expand Up @@ -364,6 +403,23 @@ export class LsifDatabase {
}
return result;
}
// Convert an old location to a new location and push the result if its still valid.
private pushIfValidLocation(array : lsp.Location[], old_loc : lsp.Location) {
let new_loc = this.oldLocToNewLoc(old_loc);
if (new_loc != null){
array.push(new_loc)
}
}

private oldLocToNewLoc (old_loc : lsp.Location) : lsp.Location | null {
let new_start = this.oldToNew(old_loc.range.start)
let new_end = this.oldToNew(old_loc.range.end)
if (new_start == null || new_end == null){
return null
} else {
return (lsp.Location.create(old_loc.uri, { start : new_start, end : new_end }))
}
}

private resolveReferenceResult(value: ReferenceResult, includeDeclaration: boolean): ResolvedReferenceResult {
let references: (Range | lsp.Location)[] | undefined;
Expand Down Expand Up @@ -450,12 +506,18 @@ export class LsifDatabase {
return;
}
let document = this.in.contains.get(value.id)!;
result.push(lsp.Location.create((document as Document).uri, this.asRange(value)));
let loc = lsp.Location.create((document as Document).uri, this.asRange(value));
this.pushIfValidLocation(result, loc);
result.push();
dedup.add(value.id);
}
}

private findRangeFromPosition(file: string, position: lsp.Position): Range | undefined {
private findRangeFromPosition(file: string, position_new: lsp.Position): Range | undefined {
let position = this.newToOld(position_new);
if (position == null){
return undefined;
}
let document = this.indices.documents.get(file);
if (document === void 0) {
return undefined;
Expand Down Expand Up @@ -538,4 +600,87 @@ export class LsifDatabase {
}
return true;
}
// This function tries to map positions in the database to new positions in an edited document.
// It was implemented in Haskell by Zubin Duggal and Luke Lau.
// https://github.com/haskell/haskell-ide-engine/blob/master/src/Haskell/Ide/Engine/Transport/LspStdio.hs#L268
private static updatePosition (dir: String) : (p : lsp.Position | null, ce : lsp.TextDocumentContentChangeEvent) => lsp.Position | null {
return function update_position(p : lsp.Position | null, ce : lsp.TextDocumentContentChangeEvent) : lsp.Position | null {

if (p == null){
return null;
}
// Pattern matching on the arguments
let l = p.line
let c = p.character

let sl = ce.range!.start.line
let sc = ce.range!.start.character

let el = ce.range!.end.line
let ec = ce.range!.end.character

// Where clause
let txt = ce.text
let oldL = el - sl
let lines = txt.split("\n")
let newL = (lines.length - 1)
let nec = (newL == 0 ) ? sc + txt.length : lines[lines.length - 1].length

let plusMinus = function (x : number, y : number) { return (dir == "forward" ? x - y : x + y); };

let dl = newL - oldL
let l1 = plusMinus(l, dl)

// pos is before the change - unaffected
if (l < sl) { console.log("Case 1"); return p; };
// pos is somewhere after the changed line,
// move down the pos to keep it the same
if (l > el) { p.line = l1;
return p; };
//
// LEGEND:
// 0-9 char index
// x untouched char
// I/i inserted/replaced char
// . deleted char
// ^ pos to be converted
//
//
//
// 012345 67
// xxxxxx xx
// ^
// 0123456789
// xxIIIIiixx
// ^
// pos is unchanged if before the edited range

if (l == sl && c <= sc) {
return p; };

// 01234 56
// xxxxx xx
// ^
// 012345678
// xxIIIiixx
// ^
// If pos is in the affected range move to after the range
if (l == sl && l == el && c <= nec && newL == 0)
{ p.character = ec;
return p; };
//
// 01234 56
// xxxxx xx
// ^
// 012345678
// xxIIIiixx
// ^
// If pos is after the affected range, update the char index
// to keep it in the same place
if (l == sl && l == el && c > nec && newL == 0)
{ p.character = plusMinus (c, (nec - sc));
return p;}
// Oh well, we tried but we can't work out where the range came from.
return null;
}}
}
16 changes: 13 additions & 3 deletions server/src/lsifServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as fs from 'fs';
const exists = promisify(fs.exists);

import URI from 'vscode-uri';
import { createConnection, ProposedFeatures, InitializeParams, TextDocumentSyncKind, WorkspaceFolder, ServerCapabilities, TextDocument, TextDocumentPositionParams, TextDocumentIdentifier, BulkUnregistration, BulkRegistration, DocumentSymbolRequest, DocumentSelector, FoldingRangeRequest, HoverRequest, DefinitionRequest, ReferencesRequest } from 'vscode-languageserver';
import { createConnection, ProposedFeatures, InitializeParams, TextDocumentSyncKind, WorkspaceFolder, TextDocumentIdentifier, BulkUnregistration, BulkRegistration, DocumentSymbolRequest, DocumentSelector, FoldingRangeRequest, HoverRequest, DefinitionRequest, ReferencesRequest } from 'vscode-languageserver';

import { LsifDatabase } from './lsifDatabase';

Expand All @@ -26,7 +26,7 @@ connection.onInitialize((params: InitializeParams) => {
folders = params.workspaceFolders;
return {
capabilities: {
textDocumentSync: TextDocumentSyncKind.None,
textDocumentSync: TextDocumentSyncKind.Incremental,
workspace: {
workspaceFolders: {
supported: true,
Expand Down Expand Up @@ -198,9 +198,19 @@ connection.onDefinition((params) => {
connection.onReferences((params) => {
let [uri, database] = getDatabase(params.textDocument);
if (!database) {

return null;
}
return database.references(uri, params.position, params.context);
});

connection.listen();

connection.onDidChangeTextDocument((params) => {
let [uri, database] = getDatabase(params.textDocument);
if (!database) {
return null;
}
return database.updateLocations(uri, params.contentChanges);
})

connection.listen();

0 comments on commit c7791de

Please sign in to comment.