This commit is contained in:
Corey Johnson 2025-10-31 10:00:06 -07:00
parent 9b57304b87
commit d195c5321c
2 changed files with 73 additions and 10 deletions

View File

@ -90,7 +90,7 @@ describe('autocomplete function names', () => {
}) })
}) })
describe('autocomplete function arguments', () => { describe('autocomplete positional arguments', () => {
test('shows args for shrimp function', () => { test('shows args for shrimp function', () => {
const args = getArgsInScope(` const args = getArgsInScope(`
add = do x y: x + y end add = do x y: x + y end
@ -123,6 +123,44 @@ describe('autocomplete function arguments', () => {
}) })
}) })
describe('autocomplete named arguments', () => {
test('shows remaining args after positional arg used', () => {
const args = getArgsInScope(`
add = do alpha bravo charlie: alpha + bravo + charlie end
add 5 <cursor>
`)
// alpha is used positionally
expect(args).toEqual(['bravo', 'charlie'])
})
test('filters args by prefix when typing', () => {
const args = getArgsInScope(`
add = do alpha bravo charlie: alpha + bravo + charlie end
add 5 b<cursor>
`)
// alpha is used, typing 'b' filters to bravo
expect(args).toEqual(['bravo'])
})
test('excludes named arg already used', () => {
const args = getArgsInScope(`
add = do alpha bravo charlie: alpha + bravo + charlie end
add bravo=10 <cursor>
`)
// bravo is used as named arg
expect(args).toEqual(['alpha', 'charlie'])
})
test('positional fills first slot, skips named args', () => {
const args = getArgsInScope(`
add = do alpha bravo charlie: alpha + bravo + charlie end
add bravo=5 <cursor>
`)
// bravo is named, 10 fills alpha (first positional slot)
expect(args).toEqual(['alpha', 'charlie'])
})
})
// Helper functions // Helper functions
const getVarsInScope = (codeWithCursor: string): CompletionItem[] => { const getVarsInScope = (codeWithCursor: string): CompletionItem[] => {
@ -163,7 +201,3 @@ const getArgsInScope = (codeWithCursor: string): string[] => {
return items.map((v) => v.name) return items.map((v) => v.name)
} }
const getArgItems = (codeWithCursor: string): CompletionItem[] => {
return getVarsInScope(codeWithCursor).filter((v) => v.kind === 'arg')
}

View File

@ -42,16 +42,19 @@ const autocompleteArg = (view: EditorView, context: FunctionCallContext): Comple
return [] return []
} }
// Count how many positional args have already been used // Collect used args
const usedPositionalArgs = countPositionalArgs(context.fnCallNode, cursor) const usedPositionalArgs = countPositionalArgs(context.fnCallNode, cursor)
const usedNamedArgs = collectUsedNamedArgs(context.fnCallNode, cursor, view)
if (fn.kind === 'shrimp-fn') { if (fn.kind === 'shrimp-fn') {
// Skip params that have already been filled positionally // Filter out named args, then skip positional slots
const remainingParams = fn.params.slice(usedPositionalArgs) const availableParams = fn.params.filter((p) => !usedNamedArgs.has(p))
const remainingParams = availableParams.slice(usedPositionalArgs)
return remainingParams.map((paramName) => ({ kind: 'arg', name: paramName })) return remainingParams.map((paramName) => ({ kind: 'arg', name: paramName }))
} else if (fn.kind === 'nose-command') { } else if (fn.kind === 'nose-command') {
// Skip params that have already been filled positionally // Filter out named args, then skip positional slots
const remainingParams = fn.def.signature.params.slice(usedPositionalArgs) const availableParams = fn.def.signature.params.filter((p) => !usedNamedArgs.has(p.name))
const remainingParams = availableParams.slice(usedPositionalArgs)
return remainingParams.map((param) => ({ return remainingParams.map((param) => ({
kind: 'arg', kind: 'arg',
name: param.name, name: param.name,
@ -266,6 +269,32 @@ const countPositionalArgs = (fnCallNode: SyntaxNode, cursor: number): number =>
return count return count
} }
const collectUsedNamedArgs = (
fnCallNode: SyntaxNode,
cursor: number,
view: EditorView
): Set<string> => {
const usedNames = new Set<string>()
let child = fnCallNode.firstChild
while (child) {
// Only collect NamedArg nodes that end before cursor
if (child.type.id === Terms.NamedArg && child.to <= cursor) {
// Find the NamedArgPrefix child which contains "paramname="
const prefixNode = child.firstChild
if (prefixNode?.type.id === Terms.NamedArgPrefix) {
const prefixText = view.state.doc.sliceString(prefixNode.from, prefixNode.to)
// Remove the trailing '=' to get the param name
const paramName = prefixText.slice(0, -1)
usedNames.add(paramName)
}
}
child = child.nextSibling
}
return usedNames
}
type ShrimpFn = { type ShrimpFn = {
kind: 'shrimp-fn' kind: 'shrimp-fn'
name: string name: string