Skip to content

Commit

Permalink
Merge pull request #350 from KxSystems/ee-params
Browse files Browse the repository at this point in the history
KXI-36324: Hotkey to cache function parameters
  • Loading branch information
ecmel authored Jun 11, 2024
2 parents 00e2f56 + 32b423b commit 0dd2850
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 32 deletions.
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@
"command": "kdb.execute.block",
"title": "KX: Execute Current q Block",
"icon": "$(run-above)"
},
{
"category": "KX",
"command": "kdb.toggleParameterCache",
"title": "KX: Toggle parameter cache"
}
],
"keybindings": [
Expand Down Expand Up @@ -422,6 +427,12 @@
"key": "ctrl+shift+e",
"mac": "cmd+shift+e",
"when": "editorLangId == q && !(resourceFilename =~ /.kdb.q/)"
},
{
"command": "kdb.toggleParameterCache",
"key": "ctrl+shift+y",
"mac": "cmd+shift+y",
"when": "editorLangId == q && !(resourceFilename =~ /.kdb.q/)"
}
],
"snippets": [
Expand Down
47 changes: 47 additions & 0 deletions server/src/qLangServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
EndOfLine,
SemiColon,
WhiteSpace,
RCurly,
} from "./parser";
import { lint } from "./linter";

Expand Down Expand Up @@ -94,6 +95,10 @@ export default class QLangServer {
"kdb.qls.expressionRange",
this.onExpressionRange.bind(this),
);
this.connection.onRequest(
"kdb.qls.parameterCache",
this.onParameterCache.bind(this),
);
}

public capabilities(): ServerCapabilities {
Expand Down Expand Up @@ -233,6 +238,48 @@ export default class QLangServer {
return expressionToRange(tokens, source.exprs);
}

public onParameterCache({
textDocument,
position,
}: TextDocumentPositionParams) {
const tokens = this.parse(textDocument);
const source = positionToToken(tokens, position);
if (!source) {
return null;
}
const lambda = inLambda(source);
if (!lambda) {
return null;
}
const scoped = tokens.filter((token) => inLambda(token) === lambda);
if (scoped.length === 0) {
return null;
}
const curly = scoped[scoped.length - 1];
if (!curly || curly.tokenType !== RCurly) {
return null;
}
const params = scoped.filter((token) => inParam(token));
if (params.length === 0) {
return null;
}
const bracket = params[params.length - 1];
if (!bracket) {
return null;
}
const args = params
.filter((token) => assigned(token))
.map((token) => token.image);
if (args.length === 0) {
return null;
}
return {
params: args,
start: rangeFromToken(bracket).end,
end: rangeFromToken(curly).start,
};
}

private parse(textDocument: TextDocumentIdentifier): Token[] {
const document = this.documents.get(textDocument.uri);
if (!document) {
Expand Down
133 changes: 133 additions & 0 deletions src/commands/clientCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright (c) 1998-2023 Kx Systems Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

import {
EndOfLine,
ExtensionContext,
Position,
Range,
Selection,
WorkspaceEdit,
commands,
workspace,
} from "vscode";
import { LanguageClient } from "vscode-languageclient/node";
import { ext } from "../extensionVariables";
import { runActiveEditor } from "./workspaceCommand";
import { ExecutionTypes } from "../models/execution";
import crypto from "crypto";

async function executeBlock(client: LanguageClient) {
if (ext.activeTextEditor) {
const range = await client.sendRequest<Range>("kdb.qls.expressionRange", {
textDocument: { uri: `${ext.activeTextEditor.document.uri}` },
position: ext.activeTextEditor.selection.active,
});
if (range) {
ext.activeTextEditor.selection = new Selection(
range.start.line,
range.start.character,
range.end.line,
range.end.character,
);
await runActiveEditor(ExecutionTypes.QuerySelection);
}
}
}

async function toggleParameterCache(client: LanguageClient) {
if (ext.activeTextEditor) {
const doc = ext.activeTextEditor.document;
const res = await client.sendRequest<{
params: string[];
start: Position;
end: Position;
}>("kdb.qls.parameterCache", {
textDocument: { uri: `${doc.uri}` },
position: ext.activeTextEditor.selection.active,
});
if (res) {
const edit = new WorkspaceEdit();
const start = new Position(res.start.line, res.start.character);
const end = new Position(res.end.line, res.end.character);
const text = doc.getText(new Range(start, end));
const match =
/\s*\.axdebug\.temp([A-F0-9]{6}).*?\.axdebug\.temp\1\s*;/s.exec(text);
if (match) {
const offset = doc.offsetAt(start);
edit.delete(
doc.uri,
new Range(
doc.positionAt(offset + match.index),
doc.positionAt(offset + match.index + match[0].length),
),
);
} else {
const hash = crypto.randomBytes(3).toString("hex").toUpperCase();
const expr1 = `.axdebug.temp${hash}: ${res.params.length === 1 ? res.params[0] : `(${res.params.join(";")})`};`;
const expr2 = `${res.params.map((param) => `\`${param}`).join("")} set${res.params.length === 1 ? "" : "'"} .axdebug.temp${hash};`;

if (start.line === end.line) {
edit.insert(doc.uri, start, " ");
edit.insert(doc.uri, start, expr1);
edit.insert(doc.uri, start, expr2);
} else {
const line = doc.getText(
new Range(end.line, 0, end.line, end.character),
);
const match = /^[ \t]*/.exec(line);
if (match) {
const eol = doc.eol === EndOfLine.CRLF ? "\r\n" : "\n";
edit.insert(doc.uri, start, eol);
edit.insert(doc.uri, start, match[0]);
edit.insert(doc.uri, start, expr1);
edit.insert(doc.uri, start, eol);
edit.insert(doc.uri, start, match[0]);
edit.insert(doc.uri, start, expr2);
}
}
}
await workspace.applyEdit(edit);
}
}
}

export function connectClientCommands(
context: ExtensionContext,
client: LanguageClient,
) {
let mutex = false;

context.subscriptions.push(
commands.registerCommand("kdb.execute.block", async () => {
if (!mutex) {
mutex = true;
try {
await executeBlock(client);
} finally {
mutex = false;
}
}
}),
commands.registerCommand("kdb.toggleParameterCache", async () => {
if (!mutex) {
mutex = true;
try {
await toggleParameterCache(client);
} finally {
mutex = false;
}
}
}),
);
}
31 changes: 2 additions & 29 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import {
ConfigurationTarget,
EventEmitter,
ExtensionContext,
Position,
Range,
Selection,
TextDocument,
TextDocumentContentProvider,
Uri,
WorkspaceEdit,
Expand Down Expand Up @@ -99,6 +96,7 @@ import { createDefaultDataSourceFile } from "./models/dataSource";
import { connectBuildTools, lintCommand } from "./commands/buildToolsCommand";
import { CompletionProvider } from "./services/completionProvider";
import { QuickFixProvider } from "./services/quickFixProvider";
import { connectClientCommands } from "./commands/clientCommands";

let client: LanguageClient;

Expand Down Expand Up @@ -398,22 +396,6 @@ export async function activate(context: ExtensionContext) {
await commands.executeCommand("deleteFile");
}
}),
commands.registerCommand("kdb.execute.block", async () => {
if (ext.activeTextEditor) {
const range = await commands.executeCommand<Range>(
"kdb.qls.expressionRange",
ext.activeTextEditor.document,
ext.activeTextEditor.selection.active,
);
if (range) {
ext.activeTextEditor.selection = new Selection(
range.start,
range.end,
);
await runActiveEditor(ExecutionTypes.QuerySelection);
}
}
}),

DataSourceEditorProvider.register(context),

Expand Down Expand Up @@ -496,16 +478,7 @@ export async function activate(context: ExtensionContext) {

await client.start();

context.subscriptions.push(
commands.registerCommand(
"kdb.qls.expressionRange",
(document: TextDocument, position: Position) =>
client.sendRequest("kdb.qls.expressionRange", {
textDocument: { uri: `${document.uri}` },
position: { line: position.line, character: position.character },
}),
),
);
connectClientCommands(context, client);

Telemetry.sendEvent("Extension.Activated");
const yamlExtension = extensions.getExtension("redhat.vscode-yaml");
Expand Down
66 changes: 65 additions & 1 deletion test/suite/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ import * as workspaceCommand from "../../src/commands/workspaceCommand";
import { MetaObject } from "../../src/models/meta";
import { WorkspaceTreeProvider } from "../../src/services/workspaceTreeProvider";
import { GetDataError } from "../../src/models/data";
import { arrayBuffer } from "stream/consumers";
import * as clientCommand from "../../src/commands/clientCommands";
import { LanguageClient } from "vscode-languageclient/node";

describe("dataSourceCommand", () => {
afterEach(() => {
Expand Down Expand Up @@ -2014,3 +2015,66 @@ describe("workspaceCommand", () => {
});
});
});

describe("clientCommands", () => {
const client = sinon.createStubInstance(LanguageClient);
let executeBlock;
let toggleParameterCache;

beforeEach(() => {
const context = <vscode.ExtensionContext>{ subscriptions: [] };
sinon.stub(vscode.commands, "registerCommand").value((a, b) => b);
clientCommand.connectClientCommands(context, client);
executeBlock = context.subscriptions[0];
toggleParameterCache = context.subscriptions[1];
ext.activeTextEditor = <vscode.TextEditor>{
options: { insertSpaces: true, indentSize: 4 },
selection: { active: new vscode.Position(0, 0) },
document: {
uri: vscode.Uri.file("/tmp/some.q"),
getText: () => "",
},
};
});
afterEach(() => {
sinon.restore();
ext.activeTextEditor = undefined;
});
describe("executeBlock", () => {
it("should execute current block", async () => {
sinon
.stub(client, "sendRequest")
.value(async () => new vscode.Range(0, 0, 1, 1));
sinon.stub(workspaceCommand, "runActiveEditor").value(() => {});
await executeBlock(client);
assert.deepEqual(
ext.activeTextEditor.selection,
new vscode.Selection(0, 0, 1, 1),
);
});
});
describe("kdb.toggleParameterCache", () => {
it("should add parameter cache for single line functions", async () => {
let edit: vscode.WorkspaceEdit;
sinon.stub(client, "sendRequest").value(async () => ({
params: ["a"],
start: new vscode.Position(0, 0),
end: new vscode.Position(0, 10),
}));
sinon.stub(vscode.workspace, "applyEdit").value(async (a) => (edit = a));
await toggleParameterCache(client);
assert.strictEqual(edit.size, 1);
});
it("should add parameter cache for multi line functions", async () => {
let edit: vscode.WorkspaceEdit;
sinon.stub(client, "sendRequest").value(async () => ({
params: ["a"],
start: new vscode.Position(0, 0),
end: new vscode.Position(1, 10),
}));
sinon.stub(vscode.workspace, "applyEdit").value(async (a) => (edit = a));
await toggleParameterCache(client);
assert.strictEqual(edit.size, 1);
});
});
});
Loading

0 comments on commit 0dd2850

Please sign in to comment.