Skip to content

Commit

Permalink
Add keywordCompletion option
Browse files Browse the repository at this point in the history
FEATURE: The new `keywordCompletion` option can be used to define what kind of completions
are generated for keywords.

Issue #17
  • Loading branch information
marijnh committed Oct 1, 2024
1 parent ff800e4 commit 5f16cc2
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 18 deletions.
14 changes: 8 additions & 6 deletions src/complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,14 @@ export function completeFromSchema(schema: SQLNamespace,
}
}

export function completeKeywords(keywords: {[name: string]: number}, upperCase: boolean) {
let completions = Object.keys(keywords).map(keyword => ({
label: upperCase ? keyword.toUpperCase() : keyword,
type: keywords[keyword] == Type ? "type" : keywords[keyword] == Keyword ? "keyword" : "variable",
boost: -1
}))
function completionType(tokenType: number) {
return tokenType == Type ? "type" : tokenType == Keyword ? "keyword" : "variable"
}

export function completeKeywords(keywords: {[name: string]: number}, upperCase: boolean,
build: (name: string, type: string) => Completion) {
let completions = Object.keys(keywords)
.map(keyword => build(upperCase ? keyword.toUpperCase() : keyword, completionType(keywords[keyword])))
return ifNotIn(["QuotedIdentifier", "SpecialVar", "String", "LineComment", "BlockComment", "."],
completeFromList(completions))
}
22 changes: 13 additions & 9 deletions src/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,18 +149,17 @@ export interface SQLConfig {
defaultSchema?: string,
/// When set to true, keyword completions will be upper-case.
upperCaseKeywords?: boolean
/// Can be used to customize the completions generated for keywords.
keywordCompletion?: (label: string, type: string) => Completion
}

function defaultKeyword(label: string, type: string) { return {label, type, boost: -1} }

/// Returns a completion source that provides keyword completion for
/// the given SQL dialect.
export function keywordCompletionSource(dialect: SQLDialect, upperCase = false): CompletionSource {
return completeKeywords(dialect.dialect.words, upperCase)
}

function keywordCompletion(dialect: SQLDialect, upperCase = false): Extension {
return dialect.language.data.of({
autocomplete: keywordCompletionSource(dialect, upperCase)
})
export function keywordCompletionSource(dialect: SQLDialect, upperCase = false,
build?: (label: string, type: string) => Completion): CompletionSource {
return completeKeywords(dialect.dialect.words, upperCase, build || defaultKeyword)
}

/// Returns a completion sources that provides schema-based completion
Expand All @@ -183,7 +182,12 @@ function schemaCompletion(config: SQLConfig): Extension {
/// extensions.
export function sql(config: SQLConfig = {}) {
let lang = config.dialect || StandardSQL
return new LanguageSupport(lang.language, [schemaCompletion(config), keywordCompletion(lang, !!config.upperCaseKeywords)])
return new LanguageSupport(lang.language, [
schemaCompletion(config),
lang.language.data.of({
autocomplete: keywordCompletionSource(lang, config.upperCaseKeywords, config.keywordCompletion)
})
])
}

/// The standard SQL dialect.
Expand Down
20 changes: 17 additions & 3 deletions test/test-complete.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import {EditorState} from "@codemirror/state"
import {CompletionContext, CompletionResult, CompletionSource} from "@codemirror/autocomplete"
import {schemaCompletionSource, PostgreSQL, MySQL, SQLConfig, SQLDialect} from "@codemirror/lang-sql"
import {schemaCompletionSource, keywordCompletionSource, PostgreSQL, MySQL, SQLConfig, SQLDialect} from "@codemirror/lang-sql"
import ist from "ist"

function get(doc: string, conf: SQLConfig & {explicit?: boolean} = {}) {
function get(doc: string, conf: SQLConfig & {explicit?: boolean, keywords?: boolean} = {}) {
let cur = doc.indexOf("|"), dialect = conf.dialect || PostgreSQL
doc = doc.slice(0, cur) + doc.slice(cur + 1)
let state = EditorState.create({
doc,
selection: {anchor: cur},
extensions: [dialect, dialect.language.data.of({
autocomplete: schemaCompletionSource(Object.assign({dialect}, conf))
autocomplete: conf.keywords
? keywordCompletionSource(dialect, conf.upperCaseKeywords, conf.keywordCompletion)
: schemaCompletionSource(Object.assign({dialect}, conf))
})]
})
let result = state.languageDataAt<CompletionSource>("autocomplete", cur)[0](new CompletionContext(state, cur, !!conf.explicit))
Expand Down Expand Up @@ -213,4 +215,16 @@ describe("SQL completion", () => {
ist(get("select foo.|", s)!.options[0].type, "constant")
ist(get("select foo.|", s)!.options.length, 1)
})

it("can complete keywords", () => {
ist(get("s|", {keywords: true})!.options.some(c => c.label == "select"))
})

it("can complete upper-case keywords", () => {
ist(get("s|", {keywords: true, upperCaseKeywords: true})!.options.some(c => c.label == "SELECT"))
})

it("can transform keyword completions", () => {
ist(get("s|", {keywords: true, keywordCompletion: l => ({label: l, type: "x"})})!.options.every(c => c.type == "x"))
})
})

0 comments on commit 5f16cc2

Please sign in to comment.