Skip to content

Commit d4801cb

Browse files
mike-lischkemzinner
authored andcommitted
Improved semantic highlighting and fixed MLE syntax bug
- For now enable MLE support unconditionally until we have the info from the backend if that is supported. - Change MyQL grammar to allow MLE syntax also for stored functions. - Added explicit highlighting for embedded language strings (double dollar strings). - Fixed SQL highlighting to work per statement. Change-Id: I4c934630930381a182c6fbb9412a7dc863559560
1 parent 702ab56 commit d4801cb

File tree

21 files changed

+439
-80
lines changed

21 files changed

+439
-80
lines changed

gui/backend/gui_plugin/core/dbms/DbSessionTasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def __init__(self, session, task_id=None, result_queue=None, params=None, result
6969
super().__init__(task_id, result_queue=result_queue,
7070
result_callback=result_callback if result_callback is not None else session.task_state_cb, options=options)
7171
self.session = session
72-
self.params = params if params else []
72+
self.params = params
7373

7474
self._start_time = None
7575
self._execution_time = None

gui/extension/src/ExtensionHost.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { ShellTasksTreeDataProvider } from "./tree-providers/ShellTreeProvider/S
3939
import { IStatusbarInfo } from "../../frontend/src/app-logic/Types.js";
4040
import { IShellModuleDataCategoriesEntry, IShellProfile } from "../../frontend/src/communication/ProtocolGui.js";
4141
import {
42+
IEditorHostExecutionOptions,
4243
IRequestListEntry, IRequestTypeMap, IRequisitionCallbackValues, IWebviewProvider, requisitions,
4344
} from "../../frontend/src/supplement/Requisitions.js";
4445
import { ShellInterface } from "../../frontend/src/supplement/ShellInterface/ShellInterface.js";
@@ -56,6 +57,7 @@ import {
5657
} from "./tree-providers/ConnectionsTreeProvider/ConnectionsTreeProvider.js";
5758
import { DBConnectionViewProvider } from "./WebviewProviders/DBConnectionViewProvider.js";
5859
import { WebviewProvider } from "./WebviewProviders/WebviewProvider.js";
60+
import { IRunQueryRequest } from "../../frontend/src/supplement/index.js";
5961

6062
/** This class manages some extension wide things like authentication handling etc. */
6163
export class ExtensionHost {
@@ -677,6 +679,22 @@ export class ExtensionHost {
677679
});
678680
}
679681

682+
case "editorExecuteOnHost": {
683+
const incoming = request.original.parameter as IEditorHostExecutionOptions;
684+
const data: IRunQueryRequest = {
685+
data: {},
686+
query: incoming.query,
687+
parameters: [],
688+
linkId: -1,
689+
};
690+
691+
return new Promise((resolve) => {
692+
void requisitions.broadcastRequest(request.provider, "editorRunQuery", data).then(() => {
693+
resolve(true);
694+
});
695+
});
696+
}
697+
680698
default:
681699
}
682700

gui/extension/src/WebviewProviders/DBConnectionViewProvider.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { readFile, writeFile } from "fs/promises";
2626
import { commands, OpenDialogOptions, SaveDialogOptions, Uri, window } from "vscode";
2727

2828
import {
29+
IEditorHostExecutionOptions,
2930
IMrsDbObjectEditRequest, IOpenDialogOptions, IOpenFileDialogResult, IRequestTypeMap, IRequisitionCallbackValues,
3031
requisitions,
3132
} from "../../../frontend/src/supplement/Requisitions.js";
@@ -356,6 +357,8 @@ export class DBConnectionViewProvider extends WebviewProvider {
356357
this.requisitions.register("editorLoadNotebook", this.editorLoadNotebook);
357358
this.requisitions.register("showOpenDialog", this.showOpenDialog);
358359
this.requisitions.register("sqlSetCurrentSchema", this.setCurrentSchema);
360+
361+
this.requisitions.register("editorExecuteOnHost", this.executeOnHost);
359362
}
360363
}
361364

@@ -397,6 +400,13 @@ export class DBConnectionViewProvider extends WebviewProvider {
397400
});
398401
};
399402

403+
private executeOnHost = (data: IEditorHostExecutionOptions): Promise<boolean> => {
404+
return requisitions.execute("proxyRequest", {
405+
provider: this,
406+
original: { requestType: "editorExecuteOnHost", parameter: data },
407+
});
408+
};
409+
400410
private showInfo = (values: string[]): Promise<boolean> => {
401411
showMessageWithTimeout(values.join("\n"), 5000);
402412

gui/frontend/src/app-logic/DialogHost.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export class DialogHost extends ComponentBase {
5050
#focusedElements: Array<Element | null> = [];
5151

5252
#promptDialogRef = createRef<ValueEditDialog>();
53-
// Tri-state: undefined = not active, null = active (use requisitions, assigned = active (use this).
53+
54+
// Tri-state: undefined = not active, null = active (use requisitions, assigned = active (use this)).
5455
#promptDialogSignal: Semaphore<IDialogResponse> | undefined | null;
5556

5657
#confirmDialogRef = createRef<ConfirmDialog>();

gui/frontend/src/components/ui/CodeEditor/CodeEditor.tsx

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ import {
3838
import { ExecutionContexts } from "../../../script-execution/ExecutionContexts.js";
3939
import { PresentationInterface } from "../../../script-execution/PresentationInterface.js";
4040
import { EditorLanguage, ITextRange } from "../../../supplement/index.js";
41-
import { appParameters, IEditorExecutionOptions, requisitions } from "../../../supplement/Requisitions.js";
41+
import {
42+
appParameters, IEditorExecutionOptions, IEditorHostExecutionOptions, requisitions,
43+
} from "../../../supplement/Requisitions.js";
4244
import { Settings } from "../../../supplement/Settings/Settings.js";
4345
import { editorRangeToTextRange } from "../../../utilities/ts-helpers.js";
4446

@@ -181,6 +183,11 @@ interface ICodeEditorProperties extends IComponentProperties {
181183
createResultPresentation?: ResultPresentationFactory;
182184
}
183185

186+
interface ITemplateItem {
187+
code: string,
188+
language: EditorLanguage,
189+
}
190+
184191
export class CodeEditor extends ComponentBase<ICodeEditorProperties> {
185192

186193
public static readonly defaultProps = {
@@ -226,6 +233,9 @@ export class CodeEditor extends ComponentBase<ICodeEditorProperties> {
226233
// All allocated event handlers and other Monaco resources that must be explicitly disposed off.
227234
private disposables: IDisposable[] = [];
228235

236+
private templateCodeBlocks: ITemplateItem[] = [];
237+
private templatePastePosition: number | undefined;
238+
229239
public constructor(props: ICodeEditorProperties) {
230240
super(props);
231241

@@ -295,10 +305,18 @@ export class CodeEditor extends ComponentBase<ICodeEditorProperties> {
295305

296306
languages.onLanguage(msg.id, () => {
297307
void msg.loader().then((definition: ILanguageDefinition) => {
308+
languages.setMonarchTokensProvider(msg.id, definition.language);
298309
languages.setLanguageConfiguration(msg.id, definition.languageConfiguration);
299310
});
300311
});
301312

313+
languages.onLanguage("mysql", () => {
314+
void msg.loader().then((definition: ILanguageDefinition) => {
315+
languages.setMonarchTokensProvider("mysql", definition.language);
316+
languages.setLanguageConfiguration("mysql", definition.languageConfiguration);
317+
});
318+
});
319+
302320
const editorLanguages = ["msg", "javascript", "typescript", "sql", "mysql", "python"];
303321
languages.registerDocumentSemanticTokensProvider(editorLanguages, new MsgSemanticTokensProvider());
304322
languages.registerCompletionItemProvider(editorLanguages, new CodeCompletionProvider());
@@ -944,6 +962,20 @@ export class CodeEditor extends ComponentBase<ICodeEditorProperties> {
944962
}));
945963

946964
if (appParameters.embedded) {
965+
this.disposables.push(editor.addAction({
966+
id: "executeOnHost",
967+
label: "Execute on all visible DB Notebooks",
968+
keybindings: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter],
969+
contextMenuGroupId: "2_execution",
970+
contextMenuOrder: 5,
971+
precondition,
972+
run: () => {
973+
const options = { atCaret: true, advance: true, asText: false };
974+
this.executeCurrentContextOnHost(options);
975+
this.executeCurrentContext(options);
976+
},
977+
}));
978+
947979
// In embedded mode some key combinations don't work by default. So we add handlers for them here.
948980
this.disposables.push(editor.addAction({
949981
id: "paste",
@@ -1099,6 +1131,34 @@ export class CodeEditor extends ComponentBase<ICodeEditorProperties> {
10991131
},
11001132
}));
11011133

1134+
this.disposables.push(editor.addAction({
1135+
id: "removeAllResults",
1136+
label: "Remove All Results",
1137+
contextMenuGroupId: "3_results",
1138+
contextMenuOrder: 1,
1139+
precondition,
1140+
run: () => { return this.removeAllResults(); },
1141+
}));
1142+
1143+
this.disposables.push(editor.addAction({
1144+
id: "useNotebookAsTemplate",
1145+
label: "Use Notebook as Template",
1146+
contextMenuGroupId: "9_cutcopypaste",
1147+
contextMenuOrder: 5,
1148+
precondition,
1149+
run: () => { return this.useNotebookAsTemplate(); },
1150+
}));
1151+
1152+
this.disposables.push(editor.addAction({
1153+
id: "appendFromTemplate",
1154+
label: "Append Codeblock from Template",
1155+
keybindings: [KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyP],
1156+
contextMenuGroupId: "9_cutcopypaste",
1157+
contextMenuOrder: 6,
1158+
precondition,
1159+
run: () => { return this.appendFromTemplate(); },
1160+
}));
1161+
11021162
this.disposables.push(editor);
11031163
this.disposables.push(editor.onDidChangeCursorPosition((e: Monaco.ICursorPositionChangedEvent) => {
11041164
if (language === "msg") {
@@ -1486,6 +1546,32 @@ export class CodeEditor extends ComponentBase<ICodeEditorProperties> {
14861546
}
14871547
}
14881548

1549+
/**
1550+
* Executes the context where the caret is in on all visible DB notebooks, if the app is embedded.
1551+
*
1552+
* @param options The options to control the execution.
1553+
*/
1554+
private executeCurrentContextOnHost(options: ICodeExecutionOptions): void {
1555+
const editor = this.backend;
1556+
const model = this.model;
1557+
if (editor && model) {
1558+
const index = this.currentBlockIndex;
1559+
if (index > -1) {
1560+
const block = model.executionContexts?.contextAt(index);
1561+
if (block) {
1562+
const data: IEditorHostExecutionOptions = {
1563+
startNewBlock: options.advance ?? true,
1564+
forceSecondaryEngine: options.forceSecondaryEngine ?? false,
1565+
asText: options.asText ?? false,
1566+
query: block.code,
1567+
language: block.language,
1568+
};
1569+
void requisitions.executeRemote("editorExecuteOnHost", data);
1570+
}
1571+
}
1572+
}
1573+
}
1574+
14891575
private runContextCommand(command: string): void {
14901576
const model = this.model;
14911577
if (model) {
@@ -2074,6 +2160,47 @@ export class CodeEditor extends ComponentBase<ICodeEditorProperties> {
20742160
}
20752161
};
20762162

2163+
/**
2164+
* Removes the result views from all execution contexts.
2165+
*/
2166+
private removeAllResults(): void {
2167+
const model = this.model;
2168+
if (model) {
2169+
model.executionContexts?.removeAllResults();
2170+
}
2171+
}
2172+
2173+
private useNotebookAsTemplate = (): void => {
2174+
const model = this.model;
2175+
if (model && model.executionContexts) {
2176+
this.templateCodeBlocks = [];
2177+
this.templatePastePosition = 0;
2178+
for (let i = 0; i < model.executionContexts.count; i++) {
2179+
const context = model.executionContexts.contextAt(i);
2180+
this.templateCodeBlocks.push({
2181+
code: context?.code ?? "",
2182+
language: context?.language ?? "sql",
2183+
});
2184+
}
2185+
2186+
this.clear();
2187+
}
2188+
};
2189+
2190+
private appendFromTemplate = (): void => {
2191+
if (this.templatePastePosition !== undefined && this.templatePastePosition < this.templateCodeBlocks.length) {
2192+
const templateBlock = this.templateCodeBlocks[this.templatePastePosition];
2193+
this.appendText(templateBlock.code);
2194+
this.switchCurrentLanguage(templateBlock.language, false);
2195+
2196+
this.templatePastePosition++;
2197+
if (this.templatePastePosition >= this.templateCodeBlocks.length) {
2198+
this.templatePastePosition = undefined;
2199+
this.templateCodeBlocks = [];
2200+
}
2201+
}
2202+
};
2203+
20772204
static {
20782205
CodeEditor.configureMonaco();
20792206

gui/frontend/src/components/ui/CodeEditor/MsgSemanticTokensProvider.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ export class MsgSemanticTokensProvider implements DocumentSemanticTokensProvider
104104
this.#changedLines.set(model.id, details);
105105
}
106106

107+
if (details.firstLine === -1 || details.lastLine === -1) {
108+
// No changes since the last call to provideDocumentSemanticTokens. That usually means that the
109+
// model was loaded from state and we have to fully generate the semantic tokens.
110+
details.firstLine = 1;
111+
details.lastLine = model.getLineCount();
112+
}
113+
107114
const indices = model.executionContexts.contextIndicesFromRange({
108115
startLine: details.firstLine,
109116
startColumn: 0,

gui/frontend/src/components/ui/CodeEditor/languages/msg/msg.contribution.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import { languages } from "../../index.js";
2525

2626
export interface ILanguageDefinition {
27+
language: languages.IMonarchLanguage;
2728
languageConfiguration: languages.LanguageConfiguration;
2829
}
2930

gui/frontend/src/components/ui/CodeEditor/languages/msg/msg.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,9 @@
2121
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2222
*/
2323

24-
/* spell-checker: disable */
25-
2624
import { languages } from "monaco-editor/esm/vs/editor/editor.api.js";
2725
import { conf as tsConfig } from "monaco-editor/esm/vs/basic-languages/typescript/typescript";
2826

29-
3027
export const languageConfiguration: languages.LanguageConfiguration = {
3128
...tsConfig,
3229
};
@@ -36,7 +33,10 @@ export const language: languages.IMonarchLanguage = {
3633
ignoreCase: true,
3734
start: "msg",
3835
tokenizer: {
39-
msg: [], // No rules here for the moment.
36+
msg: [
37+
// By default all text is formatted as string. The semantic highlighter will update the formatting.
38+
[/.*/, { token: "string.quoted.double.sql" }],
39+
],
4040
},
4141

4242
};

gui/frontend/src/modules/db-editor/DBConnectionTab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,7 @@ Execute \\help or \\? for help;`;
938938
}
939939
} else {
940940
const statements = context.statements;
941+
//const statements = await context.getAllStatements();
941942
while (true) {
942943
// Allow toggling the stop-on-error during execution.
943944
const stopOnErrors = Settings.get("editor.stopOnErrors", true);

gui/frontend/src/parsing/SQLite/SQLiteParsingServices.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,21 +180,21 @@ export class SQLiteParsingServices {
180180
*
181181
* @returns The information about the symbol at the given offset, if there's one.
182182
*/
183-
public tokenize(text: string): ITokenInfo[] {
183+
public async tokenize(text: string): Promise<ITokenInfo[]> {
184184
this.errors = [];
185185
this.lexer.inputStream = CharStreams.fromString(text);
186186
this.tokenStream.setTokenSource(this.lexer);
187187
this.tokenStream.fill();
188188

189-
return this.tokenStream.getTokens().map((token: Token) => {
189+
return Promise.resolve(this.tokenStream.getTokens().map((token: Token) => {
190190
return {
191191
type: this.lexerTypeToScope(token),
192192
offset: token.start,
193193
line: token.line,
194194
column: token.column,
195195
length: token.stop - token.start + 1,
196196
};
197-
});
197+
}));
198198
}
199199

200200
/**

0 commit comments

Comments
 (0)