Skip to content

Commit 2e6eaaa

Browse files
mike-lischkemzinner
authored andcommitted
Fixed code completion + semantic highlightingt:wq
- Both invocation on keypress and after a dot char are now working again. - Semantic highlighting now works properly also when more than one query starts or ends on the same line. - Additionally it was optimized to avoid creating many promises for large scripts. Change-Id: Id06cfd25bfdd3235103d12b66bb77b31e54613f9
1 parent 983ced4 commit 2e6eaaa

File tree

11 files changed

+238
-242
lines changed

11 files changed

+238
-242
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,8 @@ export class CodeEditor extends ComponentBase<ICodeEditorProperties> {
433433
"links": detectLinks,
434434
"colorDecorators": true,
435435
"contextmenu": model?.editorMode !== CodeEditorMode.Terminal,
436-
"suggest": suggest ?? {preview: true},
437-
"inlineSuggest": {enabled: true},
436+
"suggest": suggest ?? { preview: true },
437+
"inlineSuggest": { enabled: true },
438438
"quickSuggestions": true,
439439
"emptySelectionClipboard": false,
440440
"copyWithSyntaxHighlighting": true,

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

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -117,23 +117,6 @@ export interface IExecutionContextState {
117117
statements: IStatementSpan[];
118118
}
119119

120-
/** A statement consists of its text and its relative position within its block/context. */
121-
export interface IStatement {
122-
/** The index of the statement in the statement list. */
123-
index: number;
124-
125-
text: string;
126-
127-
/** The character offset of the statement in the containing model. */
128-
offset: number;
129-
130-
/** The line number of the statement in the containing model. */
131-
line: number;
132-
133-
/** Ditto for the column. */
134-
column: number;
135-
}
136-
137120
/** Options that control how SQL statements are executed. */
138121
export interface IScriptExecutionOptions {
139122
/** Any additional named parameters for placeholders in the query. */

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ export const language: languages.IMonarchLanguage = {
3434
start: "msg",
3535
tokenizer: {
3636
msg: [
37-
// By default all text is formatted as string. The semantic highlighter will update the formatting.
38-
[/.*/, { token: "string.quoted.double.sql" }],
37+
// Override highlighting of braces and make them appear as strings.
38+
// The semantic highlighter will update the formatting.
39+
[/[[\](){}]/, { token: "string" }],
3940
],
4041
},
4142

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,9 +931,9 @@ Execute \\help or \\? for help;`;
931931

932932
context.clearResult();
933933
if (options.source) {
934-
const sql = context.getStatementAtPosition(options.source)?.text ?? "";
934+
const sql = (await context.getStatementAtPosition(options.source))?.text;
935935

936-
await this.executeQuery(context, 0, 0, pageSize, options, sql);
936+
await this.executeQuery(context, 0, 0, pageSize, options, sql ?? "");
937937
} else {
938938
const statements = await context.getExecutableStatements();
939939
while (true) {

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

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ import {
2929
} from "antlr4ng";
3030

3131
import {
32-
ICompletionData, IParserErrorInfo, IStatementSpan, ISymbolInfo, ITokenInfo, QueryType, StatementFinishState,
33-
tokenFromPosition,
32+
ICompletionData, IParserErrorInfo, IStatement, IStatementSpan, ISymbolInfo, ITokenInfo, QueryType,
33+
StatementFinishState, tokenFromPosition,
3434
} from "../parser-common.js";
3535

3636
import { SQLiteErrorListener } from "./SQLiteErrorListener.js";
@@ -174,27 +174,33 @@ export class SQLiteParsingServices {
174174
}
175175

176176
/**
177-
* Creates a list of token info items for the given text.
177+
* Creates a list of token info items for the given statements.
178178
*
179-
* @param text The text to handle.
179+
* @param statements A list of statements to tokenize.
180180
*
181181
* @returns The information about the symbol at the given offset, if there's one.
182182
*/
183-
public async tokenize(text: string): Promise<ITokenInfo[]> {
183+
public async tokenize(statements: IStatement[]): Promise<ITokenInfo[]> {
184184
this.errors = [];
185-
this.lexer.inputStream = CharStreams.fromString(text);
186-
this.tokenStream.setTokenSource(this.lexer);
187-
this.tokenStream.fill();
188185

189-
return Promise.resolve(this.tokenStream.getTokens().map((token: Token) => {
190-
return {
191-
type: this.lexerTypeToScope(token),
192-
offset: token.start,
193-
line: token.line,
194-
column: token.column,
195-
length: token.stop - token.start + 1,
196-
};
197-
}));
186+
const result: ITokenInfo[] = [];
187+
for (const statement of statements) {
188+
this.lexer.inputStream = CharStreams.fromString(statement.text);
189+
this.tokenStream.setTokenSource(this.lexer);
190+
this.tokenStream.fill();
191+
192+
const tokens = this.tokenStream.getTokens();
193+
for (const token of tokens) {
194+
result.push({
195+
type: this.lexerTypeToScope(token),
196+
offset: token.start + statement.offset,
197+
length: token.stop - token.start + 1,
198+
});
199+
}
200+
}
201+
202+
return Promise.resolve(result);
203+
198204
}
199205

200206
/**

gui/frontend/src/parsing/mysql/MySQLParsingServices.ts

Lines changed: 46 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ import {
3838
import { MySQLErrorListener } from "./MySQLErrorListener.js";
3939
import { MySQLParseUnit } from "./MySQLServiceTypes.js";
4040
import {
41-
ICompletionData, IParserErrorInfo, IStatementSpan, ISymbolInfo, ITokenInfo, QueryType, StatementFinishState,
42-
SymbolKind,
43-
TextSpan, tokenFromPosition,
41+
ICompletionData, IParserErrorInfo, IStatement, IStatementSpan, ISymbolInfo, ITokenInfo, QueryType,
42+
StatementFinishState, SymbolKind, TextSpan, tokenFromPosition,
4443
} from "../parser-common.js";
4544

4645
import { SystemVariableSymbol, SystemFunctionSymbol, DBSymbolTable } from "../DBSymbolTable.js";
@@ -255,87 +254,64 @@ export class MySQLParsingServices {
255254
/**
256255
* Creates a list of token info items for the given text.
257256
*
258-
* @param text The text to handle.
257+
* @param statements A list of statements to tokenize.
259258
* @param serverVersion The version of MySQL to control the parsing process.
260259
* @param sqlMode The current SQL mode in the server.
261260
*
262261
* @returns The information about the symbol at the given offset, if there's one.
263262
*/
264-
public async tokenize(text: string, serverVersion: number, sqlMode: string): Promise<ITokenInfo[]> {
263+
public async tokenize(statements: IStatement[], serverVersion: number,
264+
sqlMode: string): Promise<ITokenInfo[]> {
265265
const result: ITokenInfo[] = [];
266266

267-
const splitMultiLineStatements = (text: string, type: string, firstIndex: number,
268-
firstColumn: number): void => {
269-
const lines = text.split("\n");
270-
lines.forEach((line: string, lineIndex: number) => {
271-
if (line.length > 0) {
272-
result.push({
273-
type,
274-
line: firstIndex + lineIndex,
275-
column: lineIndex === 0 ? firstColumn : 0,
276-
length: line.length,
277-
});
278-
}
279-
});
280-
281-
};
282-
283-
// Special treatment for delimiters.
284-
if (text.match(/^\s*delimiter /i)) {
285-
splitMultiLineStatements(text, "delimiter.sql", 1, 0);
286-
287-
return result;
288-
}
289-
290-
this.applyServerDetails(serverVersion, sqlMode);
291-
this.errors = [];
292-
this.lexer.inputStream = CharStreams.fromString(text);
293-
this.tokenStream.setTokenSource(this.lexer);
294-
this.tokenStream.fill();
267+
for (const statement of statements) {
268+
// Special treatment for delimiters.
269+
if (statement.text.match(/^\s*delimiter /i)) {
270+
// Line and column values are one based.
271+
result.push({
272+
type: "delimiter.sql",
273+
offset: statement.offset,
274+
length: statement.text.length,
275+
});
295276

296-
const tokens = this.tokenStream.getTokens();
277+
continue;
278+
}
297279

298-
let variablePending = false;
299-
const version = numberToVersion(serverVersion);
300-
for (const token of tokens) {
301-
switch (token.type) {
302-
case MySQLMRSLexer.WHITESPACE:
303-
case MySQLMRSLexer.EOF: {
304-
break;
305-
}
280+
this.applyServerDetails(serverVersion, sqlMode);
281+
this.errors = [];
282+
this.lexer.inputStream = CharStreams.fromString(statement.text);
283+
this.tokenStream.setTokenSource(this.lexer);
284+
this.tokenStream.fill();
306285

307-
case MySQLMRSLexer.BLOCK_COMMENT: {
308-
// Block comments can span multiple lines. Split them up into individual lines.
309-
splitMultiLineStatements(token.text, "comment.block.sql", token.line, token.column);
286+
const tokens = this.tokenStream.getTokens();
310287

311-
break;
312-
}
288+
let variablePending = false;
289+
const version = numberToVersion(serverVersion);
290+
for (const token of tokens) {
291+
switch (token.type) {
292+
case MySQLMRSLexer.WHITESPACE:
293+
case MySQLMRSLexer.EOF: {
294+
break;
295+
}
313296

314-
case MySQLMRSLexer.DOLLAR_QUOTED_STRING_TEXT: {
315-
// Ditto for dollar quoted strings.
316-
splitMultiLineStatements(token.text, "string.sql", token.line, token.column);
297+
default: {
298+
let type = await this.lexerTypeToScope(token, version);
317299

318-
break;
319-
}
300+
// System variables appear as two tokens, the first one being the @@ sign.
301+
// Make both of them appear as variable.predefined.
302+
if (type === "variable.predefined") {
303+
variablePending = true;
304+
} else if (variablePending) {
305+
variablePending = false;
306+
type = "variable.predefined";
307+
}
320308

321-
default: {
322-
let type = await this.lexerTypeToScope(token, version);
323-
324-
// System variables appear as two tokens, the first one being the @@ sign.
325-
// Make both of them appear as variable.predefined.
326-
if (type === "variable.predefined") {
327-
variablePending = true;
328-
} else if (variablePending) {
329-
variablePending = false;
330-
type = "variable.predefined";
309+
result.push({
310+
type: type + ".sql",
311+
offset: statement.offset + token.start,
312+
length: token.stop - token.start + 1,
313+
});
331314
}
332-
333-
result.push({
334-
type: type + ".sql",
335-
line: token.line,
336-
column: token.column,
337-
length: token.stop - token.start + 1,
338-
});
339315
}
340316
}
341317
}
@@ -1031,6 +1007,7 @@ export class MySQLParsingServices {
10311007
return "string.quoted.single";
10321008
}
10331009

1010+
case MySQLMRSLexer.DOLLAR_QUOTED_STRING_TEXT:
10341011
case MySQLMRSLexer.DOUBLE_QUOTED_TEXT: {
10351012
return "string.quoted.double";
10361013
}

gui/frontend/src/parsing/parser-common.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,10 @@ export interface IStatementSpan {
241241
*/
242242
delimiter?: string;
243243

244-
/** Start and length of the entire statement, including leading whitespaces. */
244+
/** Start and length of the entire statement, including leading whitespaces/comments. */
245245
span: TextSpan;
246246

247+
/** An alternative start offset, for the first non-whitespace/non-comment character. */
247248
contentStart: number;
248249

249250
/** The offset where non-whitespace content starts. */
@@ -313,11 +314,8 @@ export interface ITokenInfo {
313314
/** The token type (highlighter scope). */
314315
type: string;
315316

316-
/** The (one-based) token line. */
317-
line: number;
318-
319-
/** The (zero-based) offset in that line. */
320-
column: number;
317+
/** The position of the first token character relative to its containing statement. */
318+
offset: number;
321319

322320
/** The length of the token text. */
323321
length: number;
@@ -448,10 +446,32 @@ interface ILanguageWorkerCleanupData {
448446
language: ServiceLanguage;
449447
}
450448

449+
/** A statement consists of its text and its relative position within its block/context. */
450+
export interface IStatement {
451+
/** The index of the statement in the statement list. */
452+
index: number;
453+
454+
/** The text of the statement without delimiter. */
455+
text: string;
456+
457+
/** The character offset of the statement in the containing model. */
458+
offset: number;
459+
460+
/** The line number of the statement in the containing model. */
461+
line: number;
462+
463+
/** Ditto for the column. */
464+
column: number;
465+
466+
467+
/** The length of the delimiter given for this statement. */
468+
delimiterLength?: number;
469+
}
470+
451471
export interface ILanguageWorkerTokenizeData {
452472
api: "tokenize";
453473
language: ServiceLanguage;
454-
sql: string;
474+
statements: IStatement[];
455475
version: number;
456476
sqlMode: string;
457477
}

gui/frontend/src/parsing/worker/RdbmsLanguageService.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,12 @@ export class RdbmsLanguageService {
123123
this.localSymbols.addDependencies(model.symbols);
124124
}
125125

126-
return new Promise((resolve, reject) => {
127-
const statement = context.getStatementAtPosition(position);
128-
if (!statement) {
129-
resolve(undefined);
130-
131-
return;
132-
}
126+
const statement = await context.getStatementAtPosition(position);
127+
if (!statement) {
128+
return;
129+
}
133130

131+
return new Promise((resolve, reject) => {
134132
const infoData: ILanguageWorkerSuggestionData = {
135133
api: "suggestion",
136134
language: this.language,
@@ -148,7 +146,7 @@ export class RdbmsLanguageService {
148146
// a whitespace character or the start of the line.
149147
const line = model.getLineContent(position.lineNumber);
150148
let index = position.column - 1;
151-
while (index > 0 && !/\s/.test(line[index - 1])) {
149+
while (index > 0 && !/[\s.]/.test(line[index - 1])) {
152150
--index;
153151
}
154152

gui/frontend/src/parsing/worker/sql.worker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ ctx.addEventListener("message", (event: MessageEvent) => {
147147
}
148148

149149
case "tokenize": {
150-
void services.tokenize(data.sql, data.version, data.sqlMode).then((result) => {
150+
void services.tokenize(data.statements, data.version, data.sqlMode).then((result) => {
151151
postResultMessage(taskId, {
152152
tokens: result,
153153
final: true,

0 commit comments

Comments
 (0)