-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: cql autocompleter with syntax highlighter #46
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
fdebab0
feat: cql autocompleter with syntax highlighter
danielhe4rt 6ca3a25
fix: generate cql autocomplete
gvieira18 e2acc73
wip: autocompleter with defined cases
danielhe4rt e8770a5
Merge remote-tracking branch 'origin' into feat/syntax-highlight
danielhe4rt 969279d
fix: context to autocompleter
danielhe4rt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pq não move essas constantes para um arquivo separado para facilitar a leitura?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yo soy burrito mi cabeça no hablas frontend
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point, the file
cql-autocompleter.ts
was created to be this abstraction.I think we can keep these constants in the same file for now, since we don't know how far the scope of the implementation will grow.
See #48