Skip to content

Commit

Permalink
implemented core functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
ecmel committed Oct 2, 2024
1 parent 3e2e279 commit 1500b58
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 105 deletions.
238 changes: 154 additions & 84 deletions server/src/qLangServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import {
Diagnostic,
DiagnosticSeverity,
DidChangeConfigurationParams,
DidChangeWatchedFilesParams,
DocumentSymbol,
DocumentSymbolParams,
FileChangeType,
InitializeParams,
LSPAny,
Location,
Expand All @@ -47,6 +49,8 @@ import {
TextEdit,
WorkspaceEdit,
} from "vscode-languageserver/node";
import { glob } from "glob";
import { fileURLToPath, pathToFileURL } from "node:url";
import {
FindKind,
Token,
Expand All @@ -69,24 +73,37 @@ import {
RCurly,
} from "./parser";
import { lint } from "./linter";
import { readFile } from "node:fs";

interface Settings {
debug: boolean;
linting: boolean;
refactoring: "Workspace" | "Window";
}

const defaultSettings: Settings = { debug: false, linting: false };
const defaultSettings: Settings = {
debug: false,
linting: false,
refactoring: "Workspace",
};

interface Tokenized {
uri: string;
tokens: Token[];
}

export default class QLangServer {
private declare connection: Connection;
private declare params: InitializeParams;
private declare settings: Settings;
private declare cached: Map<string, Token[]>;
public declare documents: TextDocuments<TextDocument>;

constructor(connection: Connection, params: InitializeParams) {
this.connection = connection;
this.params = params;
this.settings = defaultSettings;
this.cached = new Map();
this.documents = new TextDocuments(TextDocument);
this.documents.listen(this.connection);
this.documents.onDidClose(this.onDidClose.bind(this));
Expand All @@ -96,6 +113,9 @@ export default class QLangServer {
this.connection.onDefinition(this.onDefinition.bind(this));
this.connection.onRenameRequest(this.onRenameRequest.bind(this));
this.connection.onCompletion(this.onCompletion.bind(this));
this.connection.onDidChangeWatchedFiles(
this.onDidChangeWatchedFiles.bind(this),
);
this.connection.languages.callHierarchy.onPrepare(
this.onPrepareCallHierarchy.bind(this),
);
Expand Down Expand Up @@ -133,21 +153,30 @@ export default class QLangServer {
}

public setSettings(settings: LSPAny) {
this.settings = settings;
this.settings = {
debug: settings.debug || false,
linting: settings.linting || false,
refactoring: settings.refactoring || "Workspace",
};
}

public onDidChangeConfiguration({ settings }: DidChangeConfigurationParams) {
if ("kdb" in settings) {
const kdb = settings.kdb;
this.setSettings({
debug: kdb.debug_parser === true || false,
linting: kdb.linting === true || false,
});
this.setSettings(settings.kdb);
}
}

public onDidClose({ document }: TextDocumentChangeEvent<TextDocument>) {
this.connection.sendDiagnostics({ uri: document.uri, diagnostics: [] });
public onDidChangeWatchedFiles({ changes }: DidChangeWatchedFilesParams) {
this.parseFiles(
changes.reduce((matches, change) => {
if (change.type === FileChangeType.Deleted) {
this.cached.delete(change.uri);
} else {
matches.push(fileURLToPath(change.uri));
}
return matches;
}, [] as string[]),
);
}

public onDidChangeContent({
Expand All @@ -168,6 +197,10 @@ export default class QLangServer {
}
}

public onDidClose({ document }: TextDocumentChangeEvent<TextDocument>) {
this.connection.sendDiagnostics({ uri: document.uri, diagnostics: [] });
}

public onDocumentSymbol({
textDocument,
}: DocumentSymbolParams): DocumentSymbol[] {
Expand All @@ -187,14 +220,11 @@ export default class QLangServer {
public onReferences({ textDocument, position }: ReferenceParams): Location[] {
const tokens = this.parse(textDocument);
const source = positionToToken(tokens, position);
return this.documents
.all()
return this.context({ uri: textDocument.uri, tokens })
.map((document) =>
findIdentifiers(
FindKind.Reference,
document.uri === textDocument.uri ? tokens : this.parse(document),
source,
).map((token) => Location.create(document.uri, rangeFromToken(token))),
findIdentifiers(FindKind.Reference, document.tokens, source).map(
(token) => Location.create(document.uri, rangeFromToken(token)),
),
)
.flat();
}
Expand All @@ -205,14 +235,11 @@ export default class QLangServer {
}: DefinitionParams): Location[] {
const tokens = this.parse(textDocument);
const source = positionToToken(tokens, position);
return this.documents
.all()
return this.context({ uri: textDocument.uri, tokens })
.map((document) =>
findIdentifiers(
FindKind.Definition,
document.uri === textDocument.uri ? tokens : this.parse(document),
source,
).map((token) => Location.create(document.uri, rangeFromToken(token))),
findIdentifiers(FindKind.Definition, document.tokens, source).map(
(token) => Location.create(document.uri, rangeFromToken(token)),
),
)
.flat();
}
Expand All @@ -224,13 +251,10 @@ export default class QLangServer {
}: RenameParams): WorkspaceEdit {
const tokens = this.parse(textDocument);
const source = positionToToken(tokens, position);
return this.documents.all().reduce(
const all = this.settings.refactoring === "Workspace";
return this.context({ uri: textDocument.uri, tokens }, all).reduce(
(edit, document) => {
const refs = findIdentifiers(
FindKind.Rename,
document.uri === textDocument.uri ? tokens : this.parse(document),
source,
);
const refs = findIdentifiers(FindKind.Rename, document.tokens, source);
if (refs.length > 0) {
const name = <Token>{
image: newName,
Expand All @@ -252,23 +276,20 @@ export default class QLangServer {
}: CompletionParams): CompletionItem[] {
const tokens = this.parse(textDocument);
const source = positionToToken(tokens, position);
return this.documents
.all()
return this.context({ uri: textDocument.uri, tokens })
.map((document) =>
findIdentifiers(
FindKind.Completion,
document.uri === textDocument.uri ? tokens : this.parse(document),
source,
).map((token) => {
return {
label: token.image,
labelDetails: {
detail: ` .${namespace(token)}`,
},
kind: CompletionItemKind.Variable,
insertText: relative(token, source),
};
}),
findIdentifiers(FindKind.Completion, document.tokens, source).map(
(token) => {
return {
label: token.image,
labelDetails: {
detail: ` .${namespace(token)}`,
},
kind: CompletionItemKind.Variable,
insertText: relative(token, source),
};
},
),
)
.flat();
}
Expand Down Expand Up @@ -369,52 +390,82 @@ export default class QLangServer {
}: CallHierarchyIncomingCallsParams): CallHierarchyIncomingCall[] {
const tokens = this.parse({ uri: item.uri });
const source = positionToToken(tokens, item.range.end);
return this.documents
.all()
.map((document) =>
findIdentifiers(FindKind.Reference, this.parse(document), source)
.filter((token) => !assigned(token) && item.data)
.map((token) => {
const lambda = inLambda(token);
return {
from: {
kind: lambda ? SymbolKind.Object : SymbolKind.Function,
name: token.image,
uri: document.uri,
range: rangeFromToken(lambda || token),
selectionRange: rangeFromToken(token),
},
fromRanges: [],
} as CallHierarchyIncomingCall;
}),
)
.flat();
return item.data
? this.context({ uri: item.uri, tokens })
.map((document) =>
findIdentifiers(FindKind.Reference, document.tokens, source)
.filter((token) => !assigned(token))
.map((token) => {
const lambda = inLambda(token);
return {
from: {
kind: lambda ? SymbolKind.Object : SymbolKind.Function,
name: token.image,
uri: document.uri,
range: rangeFromToken(lambda || token),
selectionRange: rangeFromToken(token),
},
fromRanges: [],
} as CallHierarchyIncomingCall;
}),
)
.flat()
: [];
}

public onOutgoingCallsCallHierarchy({
item,
}: CallHierarchyOutgoingCallsParams): CallHierarchyOutgoingCall[] {
const tokens = this.parse({ uri: item.uri });
const source = positionToToken(tokens, item.range.end);
return this.documents
.all()
.map((document) =>
findIdentifiers(FindKind.Reference, this.parse(document), source)
.filter((token) => inLambda(token) && !assigned(token) && item.data)
.map((token) => {
return {
to: {
kind: SymbolKind.Object,
name: token.image,
uri: document.uri,
range: rangeFromToken(inLambda(token)!),
selectionRange: rangeFromToken(token),
},
fromRanges: [],
} as CallHierarchyOutgoingCall;
}),
)
.flat();
return item.data
? this.context({ uri: item.uri, tokens })
.map((document) =>
findIdentifiers(FindKind.Reference, document.tokens, source)
.filter((token) => inLambda(token) && !assigned(token))
.map((token) => {
return {
to: {
kind: SymbolKind.Object,
name: token.image,
uri: document.uri,
range: rangeFromToken(inLambda(token)!),
selectionRange: rangeFromToken(token),
},
fromRanges: [],
} as CallHierarchyOutgoingCall;
}),
)
.flat()
: [];
}

public scan() {
this.connection.workspace.getWorkspaceFolders().then((folders) => {
if (folders) {
folders.forEach((folder) => {
glob(
"**/*.{q,quke}",
{ cwd: fileURLToPath(folder.uri), ignore: "node_modules/**" },
(err, matches) => {
if (!err) {
this.parseFiles(matches);
}
},
);
});
}
});
}

private parseFiles(matches: string[]) {
matches.forEach((match) =>
readFile(match, "utf-8", (err, file) => {
if (!err) {
this.cached.set(pathToFileURL(match).toString(), parse(file));
}
}),
);
}

private parse(textDocument: TextDocumentIdentifier): Token[] {
Expand All @@ -424,6 +475,25 @@ export default class QLangServer {
}
return parse(document.getText());
}

private context({ uri, tokens }: Tokenized, all = true): Tokenized[] {
if (all) {
this.documents.all().forEach((document) => {
this.cached.set(
document.uri,
document.uri === uri ? tokens : parse(document.getText()),
);
});
return Array.from(this.cached.entries(), (entry) => ({
uri: entry[0],
tokens: entry[1],
}));
}
return this.documents.all().map((document) => ({
uri: document.uri,
tokens: document.uri === uri ? tokens : parse(document.getText()),
}));
}
}

function rangeFromToken(token: Token): Range {
Expand Down
13 changes: 6 additions & 7 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ connection.onInitialized(() => {
section: "kdb",
});

if (connection.workspace) {
connection.workspace.getConfiguration("kdb").then((settings) => {
if (server) {
server.setSettings(settings);
}
});
}
connection.workspace.getConfiguration("kdb").then((settings) => {
if (server) {
server.setSettings(settings);
server.scan();
}
});
});

connection.listen();
Loading

0 comments on commit 1500b58

Please sign in to comment.