mirror of
https://github.com/modrinth/code.git
synced 2026-09-01 12:36:44 +00:00
simple autocomplete
This commit is contained in:
@@ -511,10 +511,76 @@ const DEFAULT_RULE = `#define ISSUE_TYPE "OBFUSCATED_NAMES"
|
|||||||
trace.issue_type == ISSUE_TYPE
|
trace.issue_type == ISSUE_TYPE
|
||||||
? "low"
|
? "low"
|
||||||
: null`
|
: null`
|
||||||
|
|
||||||
|
const CEL_LANGUAGE_COMPLETIONS: Ace.Completion[] = [
|
||||||
|
...['true', 'false', 'null', 'in'].map((value) => ({
|
||||||
|
value,
|
||||||
|
score: 900,
|
||||||
|
meta: 'keyword',
|
||||||
|
})),
|
||||||
|
{ caption: 'has()', snippet: 'has(${1:field})', score: 850, meta: 'macro' },
|
||||||
|
{ caption: 'size()', snippet: 'size(${1:value})', score: 850, meta: 'function' },
|
||||||
|
{ caption: 'string()', snippet: 'string(${1:value})', score: 850, meta: 'function' },
|
||||||
|
{ caption: 'int()', snippet: 'int(${1:value})', score: 850, meta: 'function' },
|
||||||
|
{ caption: '#define', snippet: '#define ${1:NAME} ${2:value}', score: 800, meta: 'preprocessor' },
|
||||||
|
]
|
||||||
|
const CEL_MEMBER_COMPLETIONS: Ace.Completion[] = [
|
||||||
|
{ caption: 'contains()', snippet: 'contains("${1:value}")', score: 800, meta: 'string' },
|
||||||
|
{ caption: 'startsWith()', snippet: 'startsWith("${1:prefix}")', score: 800, meta: 'string' },
|
||||||
|
{ caption: 'endsWith()', snippet: 'endsWith("${1:suffix}")', score: 800, meta: 'string' },
|
||||||
|
{ caption: 'matches()', snippet: 'matches("${1:pattern}")', score: 800, meta: 'regex' },
|
||||||
|
{ caption: 'all()', snippet: 'all(${1:item}, ${2:predicate})', score: 750, meta: 'list macro' },
|
||||||
|
{
|
||||||
|
caption: 'exists()',
|
||||||
|
snippet: 'exists(${1:item}, ${2:predicate})',
|
||||||
|
score: 750,
|
||||||
|
meta: 'list macro',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
caption: 'exists_one()',
|
||||||
|
snippet: 'exists_one(${1:item}, ${2:predicate})',
|
||||||
|
score: 750,
|
||||||
|
meta: 'list macro',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
caption: 'filter()',
|
||||||
|
snippet: 'filter(${1:item}, ${2:predicate})',
|
||||||
|
score: 750,
|
||||||
|
meta: 'list macro',
|
||||||
|
},
|
||||||
|
{ caption: 'map()', snippet: 'map(${1:item}, ${2:expression})', score: 750, meta: 'list macro' },
|
||||||
|
]
|
||||||
|
const CEL_COMPLETER: Ace.Completer = {
|
||||||
|
id: 'delphi-cel',
|
||||||
|
triggerCharacters: ['.'],
|
||||||
|
getCompletions(_editor, session, position, prefix, callback) {
|
||||||
|
const beforePrefix = session
|
||||||
|
.getLine(position.row)
|
||||||
|
.slice(0, Math.max(0, position.column - prefix.length))
|
||||||
|
const receiver = beforePrefix.match(/([A-Za-z_][\w.]*)\.$/)?.[1]
|
||||||
|
if (!receiver) {
|
||||||
|
callback(null, [...getCelSchemaCompletions(), ...CEL_LANGUAGE_COMPLETIONS])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaCompletions = getCelSchemaCompletions(receiver)
|
||||||
|
if (schemaCompletions.length > 0) {
|
||||||
|
callback(null, schemaCompletions)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, CEL_MEMBER_COMPLETIONS)
|
||||||
|
},
|
||||||
|
}
|
||||||
const RULE_EDITOR_OPTIONS: Partial<Ace.EditorOptions> = {
|
const RULE_EDITOR_OPTIONS: Partial<Ace.EditorOptions> = {
|
||||||
useWorker: false,
|
useWorker: false,
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
useSoftTabs: true,
|
useSoftTabs: true,
|
||||||
|
enableBasicAutocompletion: [CEL_COMPLETER],
|
||||||
|
enableLiveAutocompletion: [CEL_COMPLETER],
|
||||||
|
liveAutocompletionDelay: 150,
|
||||||
|
liveAutocompletionThreshold: 1,
|
||||||
|
enableSnippets: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleTestError = {
|
type RuleTestError = {
|
||||||
@@ -628,6 +694,7 @@ onMounted(async () => {
|
|||||||
import('vue3-ace-editor'),
|
import('vue3-ace-editor'),
|
||||||
import('@modrinth/ui/src/utils/ace-theme'),
|
import('@modrinth/ui/src/utils/ace-theme'),
|
||||||
])
|
])
|
||||||
|
await import('ace-builds/src-noconflict/ext-language_tools')
|
||||||
editorComponent.value = VAceEditor
|
editorComponent.value = VAceEditor
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -683,6 +750,70 @@ function isSchema(value: unknown): value is Labrinth.TechReview.Internal.DelphiR
|
|||||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveRuleSchema(
|
||||||
|
schema: Labrinth.TechReview.Internal.DelphiRuleSchema,
|
||||||
|
components: Record<string, Labrinth.TechReview.Internal.DelphiRuleSchema>,
|
||||||
|
): Labrinth.TechReview.Internal.DelphiRuleSchema {
|
||||||
|
let resolved = schema
|
||||||
|
const visited = new Set<string>()
|
||||||
|
|
||||||
|
while (typeof resolved.$ref === 'string') {
|
||||||
|
const name = decodeURIComponent(resolved.$ref.split('/').at(-1) ?? '')
|
||||||
|
if (!name || visited.has(name) || !components[name]) break
|
||||||
|
|
||||||
|
visited.add(name)
|
||||||
|
resolved = components[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuleSchemaLabel(
|
||||||
|
schema: Labrinth.TechReview.Internal.DelphiRuleSchema,
|
||||||
|
components: Record<string, Labrinth.TechReview.Internal.DelphiRuleSchema>,
|
||||||
|
): string {
|
||||||
|
if (typeof schema.$ref === 'string') {
|
||||||
|
return decodeURIComponent(schema.$ref.split('/').at(-1) ?? '') || 'context'
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = resolveRuleSchema(schema, components)
|
||||||
|
const types = Array.isArray(resolved.type)
|
||||||
|
? resolved.type.filter((type): type is string => typeof type === 'string')
|
||||||
|
: typeof resolved.type === 'string'
|
||||||
|
? [resolved.type]
|
||||||
|
: []
|
||||||
|
return types.join(' | ') || 'context'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCelSchemaCompletions(receiver?: string): Ace.Completion[] {
|
||||||
|
const response = ruleSchema.value
|
||||||
|
if (!response) return []
|
||||||
|
|
||||||
|
let schema = response.input
|
||||||
|
for (const segment of receiver?.split('.') ?? []) {
|
||||||
|
const resolved = resolveRuleSchema(schema, response.components)
|
||||||
|
if (!isSchema(resolved.properties)) return []
|
||||||
|
|
||||||
|
const property = resolved.properties[segment]
|
||||||
|
if (!isSchema(property)) return []
|
||||||
|
schema = property
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = resolveRuleSchema(schema, response.components)
|
||||||
|
if (!isSchema(resolved.properties)) return []
|
||||||
|
|
||||||
|
return Object.entries(resolved.properties)
|
||||||
|
.filter(
|
||||||
|
(entry): entry is [string, Labrinth.TechReview.Internal.DelphiRuleSchema] =>
|
||||||
|
isSchema(entry[1]),
|
||||||
|
)
|
||||||
|
.map(([value, property]) => ({
|
||||||
|
value,
|
||||||
|
score: 1_000,
|
||||||
|
meta: getRuleSchemaLabel(property, response.components),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
function formatRuleSchema(
|
function formatRuleSchema(
|
||||||
schema: Labrinth.TechReview.Internal.DelphiRuleSchema,
|
schema: Labrinth.TechReview.Internal.DelphiRuleSchema,
|
||||||
components: Record<string, Labrinth.TechReview.Internal.DelphiRuleSchema>,
|
components: Record<string, Labrinth.TechReview.Internal.DelphiRuleSchema>,
|
||||||
|
|||||||
Reference in New Issue
Block a user