-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: cql autocompleter with syntax highlighter (#46)
![image](https://github.com/user-attachments/assets/70c3f723-5746-4ef7-b556-0d5993608007) This Pull Requests aims to add Syntax Highlight for 'CQL' and bring Autocompletion for the Editor based on the Keyspace informations (fields, tables, keyspaces etc). - [x] Syntax Highlight with CQL - [ ] Keyspace Autocompletion At the CQL Editor you will find these lines and everything must be around it. ```ts monaco.languages.register({ id: "cql", extensions: [".cql"], aliases: ["CQL", "cql"], mimetypes: ["text/cql"], }); monaco.languages.setMonarchTokensProvider("cql", cql_language); // monaco.languages.registerCompletionItemProvider("cql", {}); // ``` Repositories used as reference: * [https://github.com/DTStack/monaco-sql-languages](https://github.com/DTStack/monaco-sql-languages) * [https://github.com/brijeshb42/monaco-ace-tokenizer](https://github.com/brijeshb42/monaco-ace-tokenizer) > [!WARNING] > There is an "compilation error" poppin so far when you try to add any completion. Most of the other projects currently uses **Webpack** plugin to handle it. I'm not sure how to solve it in NextJS. Closes #16 ---- ### Autocompletion and Syntax Highlighting Enhancements: * [`src/app/(main)/query-runner/_components/cql-autocompleter.ts`](diffhunk://#diff-5feb6defb1e3d8fa5b864f5eb133be4704cd4a41d6d808d688c8d4c0eb4fb579R1-R283): Implemented a comprehensive autocompletion feature for CQL keywords, functions, data types, and operators using the `monaco-editor` library. * [`src/app/(main)/query-runner/_components/cql-editor.tsx`](diffhunk://#diff-c3d008cee84b65d82023278c89d53a325b14f0df0e41dfe05a6b97bc57e63f16R5-R6): Integrated the CQL autocompletion provider and registered the CQL language with the Monaco editor. Updated the editor configuration to use CQL instead of SQL. [[1]](diffhunk://#diff-c3d008cee84b65d82023278c89d53a325b14f0df0e41dfe05a6b97bc57e63f16R5-R6) [[2]](diffhunk://#diff-c3d008cee84b65d82023278c89d53a325b14f0df0e41dfe05a6b97bc57e63f16L24-R32) [[3]](diffhunk://#diff-c3d008cee84b65d82023278c89d53a325b14f0df0e41dfe05a6b97bc57e63f16R71) [[4]](diffhunk://#diff-c3d008cee84b65d82023278c89d53a325b14f0df0e41dfe05a6b97bc57e63f16R180-R223) [[5]](diffhunk://#diff-c3d008cee84b65d82023278c89d53a325b14f0df0e41dfe05a6b97bc57e63f16L281-R334) * [`src/app/(main)/query-runner/_components/cql-language.ts`](diffhunk://#diff-ec0c7dc84c86fb5fde17b29d073ce9eaefb505aa1e5cdc15d9cd6feb7c50de47R1-R341): Defined the CQL language configuration and tokenizer for syntax highlighting in the Monaco editor. ### Minor Adjustments: * [`src/app/(main)/query-runner/_components/utils.tsx`](diffhunk://#diff-dc47942100a9e6a96dfd00903f3918d3286160928d04636e290421ecb58464feR24): Minor formatting adjustment to improve code readability. --------- Co-authored-by: Gabriel do Carmo Vieira <[email protected]>
- Loading branch information
1 parent
f40020a
commit d5029e3
Showing
4 changed files
with
756 additions
and
2 deletions.
There are no files selected for viewing
372 changes: 372 additions & 0 deletions
372
src/app/(main)/query-runner/_components/cql-autocompleter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,372 @@ | ||
import { useMonaco } from "@monaco-editor/react"; | ||
import KeyspaceDefinitions from "@scylla-studio/app/(main)/keyspace/[keyspace]/_components/keyspace-tables"; | ||
import { getFullQueryAtCursor } from "@scylla-studio/app/(main)/query-runner/_components/utils"; | ||
import { useCqlFilters } from "@scylla-studio/hooks/use-cql-filters"; | ||
import { KeyspaceDefinition } from "@scylla-studio/lib/cql-parser/keyspace-parser"; | ||
import { TableDefinition } from "@scylla-studio/lib/cql-parser/table-parser"; | ||
import { | ||
CompletionItem, | ||
CompletionItemLabel, | ||
editor, | ||
languages, | ||
} from "monaco-editor"; | ||
|
||
export type MonacoInstance = NonNullable<ReturnType<typeof useMonaco>>; | ||
|
||
// Define CQL keywords, functions, data types, and operators | ||
const keywords = [ | ||
"ADD", | ||
"AGGREGATE", | ||
"ALL", | ||
"ALLOW", | ||
"ALTER", | ||
"AND", | ||
"ANY", | ||
"APPLY", | ||
"AS", | ||
"ASC", | ||
"AUTHORIZE", | ||
"BATCH", | ||
"BEGIN", | ||
"BY", | ||
"CAST", | ||
"COLUMNFAMILY", | ||
"CREATE", | ||
"CUSTOM", | ||
"DELETE", | ||
"DESC", | ||
"DISTINCT", | ||
"DROP", | ||
"ENTRIES", | ||
"EXISTS", | ||
"FILTERING", | ||
"FROM", | ||
"FULL", | ||
"FUNCTION", | ||
"GRANT", | ||
"IF", | ||
"IN", | ||
"INDEX", | ||
"INFINITY", | ||
"INSERT", | ||
"INTO", | ||
"IS", | ||
"JSON", | ||
"KEY", | ||
"KEYSPACE", | ||
"KEYSPACES", | ||
"LANGUAGE", | ||
"LIMIT", | ||
"LOGIN", | ||
"MATERIALIZED", | ||
"MODIFY", | ||
"NAN", | ||
"NAMESPACE", | ||
"NORECURSIVE", | ||
"NOT", | ||
"NULL", | ||
"OF", | ||
"ON", | ||
"OPTIONS", | ||
"OR", | ||
"ORDER", | ||
"PARTITION", | ||
"PASSWORD", | ||
"PER", | ||
"PERMISSION", | ||
"PERMISSIONS", | ||
"PRIMARY", | ||
"REVOKE", | ||
"SCHEMA", | ||
"SELECT", | ||
"SET", | ||
"SFUNCTION", | ||
"STATIC", | ||
"STORAGE", | ||
"SUPERUSER", | ||
"TABLE", | ||
"TOKEN", | ||
"TRUNCATE", | ||
"TTL", | ||
"TYPE", | ||
"UNLOGGED", | ||
"UPDATE", | ||
"USE", | ||
"USER", | ||
"USERS", | ||
"USING", | ||
"VALUES", | ||
"VIEW", | ||
"WHERE", | ||
"WITH", | ||
"WRITETIME", | ||
]; | ||
|
||
const functions = [ | ||
"blobAsBigint", | ||
"blobAsBoolean", | ||
"blobAsDecimal", | ||
"blobAsDouble", | ||
"blobAsFloat", | ||
"blobAsInt", | ||
"blobAsText", | ||
"blobAsTimestamp", | ||
"blobAsUUID", | ||
"blobAsVarchar", | ||
"blobAsVarint", | ||
"dateOf", | ||
"now", | ||
"unixTimestampOf", | ||
"uuid", | ||
"timeuuid", | ||
"minTimeuuid", | ||
"maxTimeuuid", | ||
"toDate", | ||
"toTimestamp", | ||
"toUnixTimestamp", | ||
"writetime", | ||
"ttl", | ||
"token", | ||
"bigintAsBlob", | ||
"booleanAsBlob", | ||
"dateAsBlob", | ||
"decimalAsBlob", | ||
"doubleAsBlob", | ||
"floatAsBlob", | ||
"inetAsBlob", | ||
"intAsBlob", | ||
"textAsBlob", | ||
"timestampAsBlob", | ||
"timeuuidAsBlob", | ||
"uuidAsBlob", | ||
"varcharAsBlob", | ||
"varintAsBlob", | ||
]; | ||
|
||
const dataTypes = [ | ||
"ascii", | ||
"bigint", | ||
"blob", | ||
"boolean", | ||
"counter", | ||
"date", | ||
"decimal", | ||
"double", | ||
"float", | ||
"frozen", | ||
"inet", | ||
"int", | ||
"list", | ||
"map", | ||
"set", | ||
"smallint", | ||
"text", | ||
"time", | ||
"timestamp", | ||
"timeuuid", | ||
"tinyint", | ||
"tuple", | ||
"uuid", | ||
"varchar", | ||
"varint", | ||
]; | ||
|
||
const operators = [ | ||
"=", | ||
">", | ||
"<", | ||
">=", | ||
"<=", | ||
"!=", | ||
"+", | ||
"-", | ||
"*", | ||
"/", | ||
"%", | ||
"IN", | ||
"CONTAINS", | ||
"CONTAINS KEY", | ||
"AND", | ||
"OR", | ||
"NOT", | ||
"IS", | ||
"NULL", | ||
"IS NOT NULL", | ||
"IS NULL", | ||
]; | ||
|
||
const defaultRange = { | ||
startLineNumber: 0, | ||
startColumn: 0, | ||
endLineNumber: 0, | ||
endColumn: 0, | ||
}; | ||
|
||
function getDefaultSuggestions( | ||
monacoInstance: MonacoInstance, | ||
): languages.CompletionItem[] { | ||
const cqlSyntax = { | ||
[monacoInstance.languages.CompletionItemKind.Keyword]: { | ||
data: keywords, | ||
description: "Keyword", | ||
}, | ||
[monacoInstance.languages.CompletionItemKind.Function]: { | ||
data: functions, | ||
description: 'func + "($0)"', | ||
}, | ||
[monacoInstance.languages.CompletionItemKind.TypeParameter]: { | ||
data: dataTypes, | ||
description: "Data Type", | ||
}, | ||
[monacoInstance.languages.CompletionItemKind.Operator]: { | ||
data: operators, | ||
description: "Operator", | ||
}, | ||
}; | ||
|
||
return Object.entries(cqlSyntax).flatMap(([syntaxType, item]) => | ||
item.data.map( | ||
(value) => | ||
({ | ||
label: { | ||
label: value, | ||
detail: | ||
monacoInstance.languages.CompletionItemKind[ | ||
syntaxType as keyof typeof monacoInstance.languages.CompletionItemKind | ||
].toString(), | ||
description: item.description, | ||
} as languages.CompletionItemLabel, | ||
kind: monacoInstance.languages.CompletionItemKind[ | ||
syntaxType as keyof typeof monacoInstance.languages.CompletionItemKind | ||
], | ||
documentation: item.description, | ||
range: defaultRange, | ||
insertText: value, | ||
insertTextRules: | ||
monacoInstance.languages.CompletionItemInsertTextRule | ||
.InsertAsSnippet, | ||
}) as languages.CompletionItem, | ||
), | ||
); | ||
} | ||
|
||
function prepareUseSuggestions( | ||
monacoInstance: MonacoInstance, | ||
keyspaces: KeyspaceDefinition[], | ||
): languages.CompletionItem[] { | ||
return keyspaces.map( | ||
(keyspace) => | ||
({ | ||
label: { | ||
label: keyspace.name, | ||
detail: "", | ||
description: "Keyspace", | ||
} as languages.CompletionItemLabel, | ||
kind: monacoInstance.languages.CompletionItemKind.Class, | ||
documentation: "TODO: build documentation with the Keyspace Object", | ||
range: defaultRange, | ||
insertText: keyspace.name, | ||
insertTextRules: | ||
monacoInstance.languages.CompletionItemInsertTextRule.InsertAsSnippet, | ||
}) as languages.CompletionItem, | ||
); | ||
} | ||
|
||
type TableCompletion = { | ||
keyspace: string; | ||
table: string; | ||
}; | ||
|
||
function prepareTablesSuggestions( | ||
monacoInstance: MonacoInstance, | ||
tables: TableCompletion[], | ||
): languages.CompletionItem[] { | ||
return tables.map( | ||
(table) => | ||
({ | ||
label: { | ||
label: `${table.keyspace}.${table.table}`, | ||
detail: "", | ||
description: "Table", | ||
} as languages.CompletionItemLabel, | ||
kind: monacoInstance.languages.CompletionItemKind.Class, | ||
documentation: "TODO: build documentation with the Keyspace Object", | ||
range: defaultRange, | ||
insertText: `${table.keyspace}.${table.table}`, | ||
insertTextRules: | ||
monacoInstance.languages.CompletionItemInsertTextRule.InsertAsSnippet, | ||
}) as languages.CompletionItem, | ||
); | ||
} | ||
|
||
// Helper function to create completion items | ||
export const cqlCompletionItemProvider = ( | ||
monacoInstance: MonacoInstance, | ||
editor: editor.IStandaloneCodeEditor, | ||
): languages.CompletionItem[] => { | ||
const cursor = getFullQueryAtCursor(editor, monacoInstance); | ||
|
||
if (!cursor) return getDefaultSuggestions(monacoInstance); | ||
|
||
const textUntilPosition = cursor.query.split("\n\n").shift() || ""; | ||
|
||
const useKeyspaceRegex = /USE\s+(\w*)$/i; | ||
const fromTableRegex = /FROM\s+(\w*)$/i; | ||
const selectColumnRegex = /SELECT\s+([\w,\s]*)$/i; | ||
const whereColumnRegex = /WHERE\s+(\w*)$/i; | ||
|
||
// Check if you're typing a keyspace | ||
if (useKeyspaceRegex.test(textUntilPosition)) { | ||
const keyspaces = [ | ||
{ | ||
// TODO: this should be a real keyspace | ||
name: "my_keyspace", | ||
entitiesCount: 1337, // Ignore this until I figure out what it is | ||
replication: { | ||
class: "NetworkTopologyStrategy", | ||
datacenters: new Map<string, number>(), | ||
}, | ||
durableWrites: true, | ||
tablets: true, | ||
tables: new Map<string, TableDefinition>(), | ||
}, | ||
] as KeyspaceDefinition[]; | ||
|
||
return prepareUseSuggestions(monacoInstance, keyspaces); | ||
} else if (fromTableRegex.test(textUntilPosition)) { | ||
const tables = [ | ||
{ | ||
keyspace: "my_keyspace", | ||
table: "my_table", | ||
}, | ||
{ | ||
keyspace: "my_keyspace", | ||
table: "my_table2", | ||
}, | ||
] as TableCompletion[]; | ||
|
||
return prepareTablesSuggestions(monacoInstance, tables); | ||
} else if (selectColumnRegex.test(textUntilPosition)) { | ||
// TODO: or we display everything related to all tables or we display nothing | ||
return getDefaultSuggestions(monacoInstance); | ||
} else if (whereColumnRegex.test(textUntilPosition)) { | ||
// TODO: here we would need to fetch all related columns from the specific table mentioned previously | ||
return [ | ||
{ | ||
label: { | ||
label: "field1", | ||
detail: "Column", | ||
description: " - table x ", | ||
}, | ||
kind: monacoInstance.languages.CompletionItemKind.Keyword, | ||
documentation: "Column from table x", | ||
range: defaultRange, | ||
insertText: "field1", | ||
insertTextRules: | ||
monacoInstance.languages.CompletionItemInsertTextRule.InsertAsSnippet, | ||
}, | ||
]; | ||
} | ||
|
||
return getDefaultSuggestions(monacoInstance); | ||
}; |
Oops, something went wrong.