Compare commits
27 Commits
757a50e23e
...
3c539130b0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c539130b0 | |||
| a9df56346e | |||
| ad044b12dc | |||
| ee40dcc65c | |||
| 88f98f9817 | |||
| 4c91ef57b9 | |||
|
|
b0ad0a0768 | ||
|
|
ccf8f41544 | ||
|
|
6da00dd3c8 | ||
|
|
d4cb86b50b | ||
|
|
c7d4db8528 | ||
|
|
de0d43a1d6 | ||
|
|
4d718ef12b | ||
|
|
4b57728072 | ||
|
|
2c0723b8a3 | ||
|
|
c6e5c44755 | ||
|
|
1d2c85b19c | ||
|
|
52e100cab3 | ||
|
|
5010a9584c | ||
|
|
4b6f6a127f | ||
|
|
38eaed490c | ||
|
|
16cb47ddcc | ||
|
|
a5b2802c0c | ||
|
|
1b80a159c5 | ||
|
|
109c7ff9f6 | ||
| 49919e9f85 | |||
| 0bc923fc82 |
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -35,5 +35,3 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
/tmp
|
/tmp
|
||||||
/docs
|
/docs
|
||||||
|
|
||||||
*.vsix
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
echo
|
|
||||||
4
vscode-extension/.gitignore
vendored
Normal file
4
vscode-extension/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules
|
||||||
|
client/dist
|
||||||
|
server/dist
|
||||||
|
*.vsix
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"shrimp"
|
"shrimp"
|
||||||
],
|
],
|
||||||
"extensions": [
|
"extensions": [
|
||||||
".shrimp"
|
".sh"
|
||||||
],
|
],
|
||||||
"configuration": "./language-configuration.json"
|
"configuration": "./language-configuration.json"
|
||||||
}
|
}
|
||||||
|
|
@ -80,12 +80,11 @@
|
||||||
"publisher": "shrimp-lang",
|
"publisher": "shrimp-lang",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "bun run package",
|
"vscode:prepublish": "bun run package",
|
||||||
"generate-prelude-metadata": "bun scripts/generate-prelude-metadata.ts",
|
"compile": "bun run compile:client && bun run compile:server",
|
||||||
"compile": "bun run generate-prelude-metadata && bun run compile:client && bun run compile:server",
|
|
||||||
"compile:client": "bun build client/src/extension.ts --outdir client/dist --target node --format cjs --external vscode",
|
"compile:client": "bun build client/src/extension.ts --outdir client/dist --target node --format cjs --external vscode",
|
||||||
"compile:server": "bun build server/src/server.ts --outdir server/dist --target node --format cjs",
|
"compile:server": "bun build server/src/server.ts --outdir server/dist --target node --format cjs",
|
||||||
"watch": "bun run compile:client --watch & bun run compile:server --watch",
|
"watch": "bun run compile:client --watch & bun run compile:server --watch",
|
||||||
"package": "bun run generate-prelude-metadata && bun run compile:client --minify && bun run compile:server --minify",
|
"package": "bun run compile:client --minify && bun run compile:server --minify",
|
||||||
"check-types": "tsc --noEmit",
|
"check-types": "tsc --noEmit",
|
||||||
"build-and-install": "bun run package && bunx @vscode/vsce package --allow-missing-repository && code --install-extension shrimp-*.vsix"
|
"build-and-install": "bun run package && bunx @vscode/vsce package --allow-missing-repository && code --install-extension shrimp-*.vsix"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
#!/usr/bin/env bun
|
|
||||||
/**
|
|
||||||
* Generates prelude metadata for the VSCode extension.
|
|
||||||
* - Prelude names (for parser scope tracking)
|
|
||||||
* - Function signatures (for autocomplete)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { writeFileSync } from 'fs'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { globals } from '../../src/prelude'
|
|
||||||
|
|
||||||
// Extract parameter names from a function
|
|
||||||
const extractParams = (fn: Function): string[] => {
|
|
||||||
const fnStr = fn.toString()
|
|
||||||
const match = fnStr.match(/\(([^)]*)\)/)
|
|
||||||
if (!match) return []
|
|
||||||
|
|
||||||
const paramsStr = match[1]!.trim()
|
|
||||||
if (!paramsStr) return []
|
|
||||||
|
|
||||||
// Split by comma, but be careful of default values with commas
|
|
||||||
const params: string[] = []
|
|
||||||
let current = ''
|
|
||||||
let inString = false
|
|
||||||
let stringChar = ''
|
|
||||||
|
|
||||||
for (let i = 0; i < paramsStr.length; i++) {
|
|
||||||
const char = paramsStr[i]
|
|
||||||
if ((char === '"' || char === "'") && (i === 0 || paramsStr[i - 1] !== '\\')) {
|
|
||||||
if (!inString) {
|
|
||||||
inString = true
|
|
||||||
stringChar = char
|
|
||||||
} else if (char === stringChar) {
|
|
||||||
inString = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === ',' && !inString) {
|
|
||||||
params.push(current.trim())
|
|
||||||
current = ''
|
|
||||||
} else {
|
|
||||||
current += char
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (current.trim()) params.push(current.trim())
|
|
||||||
|
|
||||||
return params
|
|
||||||
.map((p) => p.split(/[=:]/)[0]!.trim()) // Handle defaults and types
|
|
||||||
.filter((p) => p && p !== 'this')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate metadata for a module
|
|
||||||
const generateModuleMetadata = (module: Record<string, any>) => {
|
|
||||||
const metadata: Record<string, { params: string[] }> = {}
|
|
||||||
|
|
||||||
for (const [name, value] of Object.entries(module)) {
|
|
||||||
if (typeof value === 'function') {
|
|
||||||
metadata[name] = { params: extractParams(value) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate names list
|
|
||||||
const names = Object.keys(globals).sort()
|
|
||||||
|
|
||||||
// Generate module metadata
|
|
||||||
const moduleMetadata: Record<string, any> = {}
|
|
||||||
for (const [name, value] of Object.entries(globals)) {
|
|
||||||
if (typeof value === 'object' && value !== null && name !== '$') {
|
|
||||||
moduleMetadata[name] = generateModuleMetadata(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate dollar metadata
|
|
||||||
const dollarMetadata: Record<string, { params: string[] }> = {}
|
|
||||||
if (globals.$ && typeof globals.$ === 'object') {
|
|
||||||
for (const key of Object.keys(globals.$)) {
|
|
||||||
dollarMetadata[key] = { params: [] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write prelude-names.ts
|
|
||||||
const namesOutput = `// Auto-generated by scripts/generate-prelude-metadata.ts
|
|
||||||
// Do not edit manually - run 'bun run generate-prelude-metadata' to regenerate
|
|
||||||
|
|
||||||
export const PRELUDE_NAMES = ${JSON.stringify(names, null, 2)} as const
|
|
||||||
`
|
|
||||||
|
|
||||||
const namesPath = join(import.meta.dir, '../server/src/metadata/prelude-names.ts')
|
|
||||||
writeFileSync(namesPath, namesOutput)
|
|
||||||
|
|
||||||
// Write prelude-completions.ts
|
|
||||||
const completionsOutput = `// Auto-generated by scripts/generate-prelude-metadata.ts
|
|
||||||
// Do not edit manually - run 'bun run generate-prelude-metadata' to regenerate
|
|
||||||
|
|
||||||
export type CompletionMetadata = {
|
|
||||||
params: string[]
|
|
||||||
description?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const completions = {
|
|
||||||
modules: ${JSON.stringify(moduleMetadata, null, 2)},
|
|
||||||
dollar: ${JSON.stringify(dollarMetadata, null, 2)},
|
|
||||||
} as const
|
|
||||||
`
|
|
||||||
|
|
||||||
const completionsPath = join(import.meta.dir, '../server/src/metadata/prelude-completions.ts')
|
|
||||||
writeFileSync(completionsPath, completionsOutput)
|
|
||||||
|
|
||||||
console.log(`✓ Generated ${names.length} prelude names to server/src/metadata/prelude-names.ts`)
|
|
||||||
console.log(
|
|
||||||
`✓ Generated completions for ${
|
|
||||||
Object.keys(moduleMetadata).length
|
|
||||||
} modules to server/src/metadata/prelude-completions.ts`
|
|
||||||
)
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import { CompletionItem, CompletionItemKind } from 'vscode-languageserver/node'
|
|
||||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
|
||||||
import { completions } from '../metadata/prelude-completions'
|
|
||||||
import { analyzeCompletionContext } from './contextAnalyzer'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides context-aware completions for Shrimp code.
|
|
||||||
* Returns module function completions (dict.*, list.*, str.*) or dollar property
|
|
||||||
* completions ($.*) based on the cursor position.
|
|
||||||
*/
|
|
||||||
export const provideCompletions = (
|
|
||||||
document: TextDocument,
|
|
||||||
position: { line: number; character: number }
|
|
||||||
): CompletionItem[] => {
|
|
||||||
const context = analyzeCompletionContext(document, position)
|
|
||||||
|
|
||||||
if (context.type === 'module') {
|
|
||||||
return buildModuleCompletions(context.moduleName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.type === 'dollar') {
|
|
||||||
return buildDollarCompletions()
|
|
||||||
}
|
|
||||||
|
|
||||||
return [] // No completions for other contexts yet
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds completion items for module functions (dict.*, list.*, str.*).
|
|
||||||
*/
|
|
||||||
const buildModuleCompletions = (moduleName: string): CompletionItem[] => {
|
|
||||||
const functions = completions.modules[moduleName as keyof typeof completions.modules]
|
|
||||||
if (!functions) return []
|
|
||||||
|
|
||||||
return Object.entries(functions).map(([name, meta]) => ({
|
|
||||||
label: name,
|
|
||||||
kind: CompletionItemKind.Method,
|
|
||||||
detail: `(${meta.params.join(', ')})`,
|
|
||||||
insertText: name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds completion items for dollar properties ($.*).
|
|
||||||
*/
|
|
||||||
const buildDollarCompletions = (): CompletionItem[] => {
|
|
||||||
return Object.entries(completions.dollar).map(([name, meta]) => ({
|
|
||||||
label: name,
|
|
||||||
kind: CompletionItemKind.Property,
|
|
||||||
insertText: name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
|
||||||
import { SyntaxNode } from '@lezer/common'
|
|
||||||
import { parser } from '../../../../src/parser/shrimp'
|
|
||||||
import * as Terms from '../../../../src/parser/shrimp.terms'
|
|
||||||
|
|
||||||
export type CompletionContext =
|
|
||||||
| { type: 'module'; moduleName: string }
|
|
||||||
| { type: 'dollar' }
|
|
||||||
| { type: 'none' }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Analyzes the document at the given position to determine what kind of
|
|
||||||
* completion context we're in (module member access, dollar property, or none).
|
|
||||||
*/
|
|
||||||
export const analyzeCompletionContext = (
|
|
||||||
document: TextDocument,
|
|
||||||
position: { line: number; character: number }
|
|
||||||
): CompletionContext => {
|
|
||||||
const offset = document.offsetAt(position)
|
|
||||||
const text = document.getText()
|
|
||||||
const tree = parser.parse(text)
|
|
||||||
|
|
||||||
// Find node at cursor - could be DotGet or Identifier inside DotGet
|
|
||||||
const node = tree.resolveInner(offset, -1)
|
|
||||||
|
|
||||||
console.log(`🔍 Node at cursor: ${node.name} (type: ${node.type.id})`)
|
|
||||||
console.log(`🔍 Parent: ${node.parent?.name} (type: ${node.parent?.type.id})`)
|
|
||||||
console.log(`🔍 Node text: "${text.slice(node.from, node.to)}"`)
|
|
||||||
|
|
||||||
const SUPPORTED_MODULES = ['dict', 'list', 'str', 'math', 'fs', 'json', 'load']
|
|
||||||
|
|
||||||
// Case 1: Incomplete DotGet (dict. or $.)
|
|
||||||
// resolveInner returns DotGet node directly
|
|
||||||
if (node.type.id === Terms.DotGet) {
|
|
||||||
const leftSide = extractLeftSide(node, text)
|
|
||||||
console.log(`✅ Case 1: DotGet found, left side: "${leftSide}"`)
|
|
||||||
if (leftSide === '$') return { type: 'dollar' }
|
|
||||||
if (SUPPORTED_MODULES.includes(leftSide)) {
|
|
||||||
return { type: 'module', moduleName: leftSide }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 2: Partial identifier (dict.g or $.e)
|
|
||||||
// resolveInner returns Identifier, parent is DotGet
|
|
||||||
if (node.type.id === Terms.Identifier && node.parent?.type.id === Terms.DotGet) {
|
|
||||||
const dotGetNode = node.parent
|
|
||||||
const leftSide = extractLeftSide(dotGetNode, text)
|
|
||||||
console.log(`✅ Case 2: Identifier in DotGet found, left side: "${leftSide}"`)
|
|
||||||
if (leftSide === '$') return { type: 'dollar' }
|
|
||||||
if (SUPPORTED_MODULES.includes(leftSide)) {
|
|
||||||
return { type: 'module', moduleName: leftSide }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`❌ No matching context found`)
|
|
||||||
return { type: 'none' }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the text of the left side of a DotGet node (the part before the dot).
|
|
||||||
*/
|
|
||||||
const extractLeftSide = (dotGetNode: SyntaxNode, text: string): string => {
|
|
||||||
const firstChild = dotGetNode.firstChild
|
|
||||||
if (!firstChild) return ''
|
|
||||||
return text.slice(firstChild.from, firstChild.to)
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { TextDocument, Position } from 'vscode-languageserver-textdocument'
|
import { TextDocument, Position } from 'vscode-languageserver-textdocument'
|
||||||
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver/node'
|
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver/node'
|
||||||
import { Tree } from '@lezer/common'
|
import { parser } from '../../../src/parser/shrimp'
|
||||||
import { Compiler } from '../../../src/compiler/compiler'
|
import { Compiler } from '../../../src/compiler/compiler'
|
||||||
import { CompilerError } from '../../../src/compiler/compilerError'
|
import { CompilerError } from '../../../src/compiler/compilerError'
|
||||||
|
|
||||||
export const buildDiagnostics = (textDocument: TextDocument, tree: Tree): Diagnostic[] => {
|
export const buildDiagnostics = (textDocument: TextDocument): Diagnostic[] => {
|
||||||
const text = textDocument.getText()
|
const text = textDocument.getText()
|
||||||
const diagnostics = getParseErrors(textDocument, tree)
|
const diagnostics = getParseErrors(textDocument)
|
||||||
|
|
||||||
if (diagnostics.length > 0) {
|
if (diagnostics.length > 0) {
|
||||||
return diagnostics
|
return diagnostics
|
||||||
|
|
@ -59,7 +59,9 @@ const unknownDiagnostic = (message: string): Diagnostic => {
|
||||||
return diagnostic
|
return diagnostic
|
||||||
}
|
}
|
||||||
|
|
||||||
const getParseErrors = (textDocument: TextDocument, tree: Tree): Diagnostic[] => {
|
const getParseErrors = (textDocument: TextDocument): Diagnostic[] => {
|
||||||
|
const tree = parser.parse(textDocument.getText())
|
||||||
|
|
||||||
const ranges: { start: Position; end: Position }[] = []
|
const ranges: { start: Position; end: Position }[] = []
|
||||||
tree.iterate({
|
tree.iterate({
|
||||||
enter(n) {
|
enter(n) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { SyntaxNode } from '@lezer/common'
|
import { SyntaxNode } from '@lezer/common'
|
||||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||||
import * as Terms from '../../../src/parser/shrimp.terms'
|
import * as Terms from '../../../src/parser/shrimp.terms'
|
||||||
import { PRELUDE_NAMES } from './metadata/prelude-names'
|
import { globals } from '../../../src/prelude'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks variables in scope at a given position in the parse tree.
|
* Tracks variables in scope at a given position in the parse tree.
|
||||||
|
|
@ -13,7 +13,8 @@ export class EditorScopeAnalyzer {
|
||||||
|
|
||||||
constructor(document: TextDocument) {
|
constructor(document: TextDocument) {
|
||||||
this.document = document
|
this.document = document
|
||||||
this.scopeCache.set(0, new Set(PRELUDE_NAMES))
|
const preludeKeys = Object.keys(globals)
|
||||||
|
this.scopeCache.set(0, new Set(preludeKeys))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,732 +0,0 @@
|
||||||
// Auto-generated by scripts/generate-prelude-metadata.ts
|
|
||||||
// Do not edit manually - run 'bun run generate-prelude-metadata' to regenerate
|
|
||||||
|
|
||||||
export type CompletionMetadata = {
|
|
||||||
params: string[]
|
|
||||||
description?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const completions = {
|
|
||||||
modules: {
|
|
||||||
"dict": {
|
|
||||||
"keys": {
|
|
||||||
"params": [
|
|
||||||
"dict"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"values": {
|
|
||||||
"params": [
|
|
||||||
"dict"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"entries": {
|
|
||||||
"params": [
|
|
||||||
"dict"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"has?": {
|
|
||||||
"params": [
|
|
||||||
"dict",
|
|
||||||
"key"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"get": {
|
|
||||||
"params": [
|
|
||||||
"dict",
|
|
||||||
"key",
|
|
||||||
"defaultValue"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"set": {
|
|
||||||
"params": [
|
|
||||||
"dict",
|
|
||||||
"key",
|
|
||||||
"value"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"merge": {
|
|
||||||
"params": [
|
|
||||||
"...dicts"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"empty?": {
|
|
||||||
"params": [
|
|
||||||
"dict"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"map": {
|
|
||||||
"params": [
|
|
||||||
"dict",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"filter": {
|
|
||||||
"params": [
|
|
||||||
"dict",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"from-entries": {
|
|
||||||
"params": [
|
|
||||||
"entries"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fs": {
|
|
||||||
"ls": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"mkdir": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"rmdir": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"pwd": {
|
|
||||||
"params": []
|
|
||||||
},
|
|
||||||
"cd": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"read": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cat": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"read-bytes": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"write": {
|
|
||||||
"params": [
|
|
||||||
"path",
|
|
||||||
"content"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"append": {
|
|
||||||
"params": [
|
|
||||||
"path",
|
|
||||||
"content"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"rm": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"params": [
|
|
||||||
"from",
|
|
||||||
"to"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"move": {
|
|
||||||
"params": [
|
|
||||||
"from",
|
|
||||||
"to"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"mv": {
|
|
||||||
"params": [
|
|
||||||
"from",
|
|
||||||
"to"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"basename": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dirname": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extname": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"join": {
|
|
||||||
"params": [
|
|
||||||
"...paths"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"resolve": {
|
|
||||||
"params": [
|
|
||||||
"...paths"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"stat": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"exists?": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"file?": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dir?": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"symlink?": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"exec?": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"chmod": {
|
|
||||||
"params": [
|
|
||||||
"path",
|
|
||||||
"mode"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"symlink": {
|
|
||||||
"params": [
|
|
||||||
"target",
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"readlink": {
|
|
||||||
"params": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"glob": {
|
|
||||||
"params": [
|
|
||||||
"pattern"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"watch": {
|
|
||||||
"params": [
|
|
||||||
"path",
|
|
||||||
"callback"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cp": {
|
|
||||||
"params": [
|
|
||||||
"from",
|
|
||||||
"to"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json": {
|
|
||||||
"encode": {
|
|
||||||
"params": [
|
|
||||||
"s"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"decode": {
|
|
||||||
"params": [
|
|
||||||
"s"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parse": {
|
|
||||||
"params": [
|
|
||||||
"s"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"stringify": {
|
|
||||||
"params": [
|
|
||||||
"s"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"list": {
|
|
||||||
"slice": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"start",
|
|
||||||
"end"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"map": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"filter": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"reject": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"reduce": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb",
|
|
||||||
"initial"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"find": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"empty?": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"contains?": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"includes?": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"has?": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"any?": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"all?": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"push": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"pop": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"shift": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unshift": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"splice": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"start",
|
|
||||||
"deleteCount",
|
|
||||||
"...items"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"insert": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"index",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"reverse": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sort": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"concat": {
|
|
||||||
"params": [
|
|
||||||
"...lists"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"flatten": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"depth"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unique": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"zip": {
|
|
||||||
"params": [
|
|
||||||
"list1",
|
|
||||||
"list2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"first": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"last": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"rest": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"take": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"drop": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"append": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"prepend": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index-of": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"item"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sum": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"count": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"partition": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"compact": {
|
|
||||||
"params": [
|
|
||||||
"list"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"group-by": {
|
|
||||||
"params": [
|
|
||||||
"list",
|
|
||||||
"cb"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"math": {
|
|
||||||
"abs": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"floor": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ceil": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"round": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"min": {
|
|
||||||
"params": [
|
|
||||||
"...nums"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"max": {
|
|
||||||
"params": [
|
|
||||||
"...nums"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"pow": {
|
|
||||||
"params": [
|
|
||||||
"base",
|
|
||||||
"exp"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sqrt": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"random": {
|
|
||||||
"params": []
|
|
||||||
},
|
|
||||||
"clamp": {
|
|
||||||
"params": [
|
|
||||||
"n",
|
|
||||||
"min",
|
|
||||||
"max"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sign": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"trunc": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"even?": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"odd?": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"positive?": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"negative?": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"zero?": {
|
|
||||||
"params": [
|
|
||||||
"n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"str": {
|
|
||||||
"join": {
|
|
||||||
"params": [
|
|
||||||
"arr",
|
|
||||||
"sep"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"split": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"sep"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"to-upper": {
|
|
||||||
"params": [
|
|
||||||
"str"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"to-lower": {
|
|
||||||
"params": [
|
|
||||||
"str"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"trim": {
|
|
||||||
"params": [
|
|
||||||
"str"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"starts-with?": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"prefix"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ends-with?": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"suffix"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"contains?": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"substr"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"empty?": {
|
|
||||||
"params": [
|
|
||||||
"str"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index-of": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"search"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"last-index-of": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"search"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"replace": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"search",
|
|
||||||
"replacement"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"replace-all": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"search",
|
|
||||||
"replacement"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"slice": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"start",
|
|
||||||
"end"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"substring": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"start",
|
|
||||||
"end"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"repeat": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"count"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"pad-start": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"length",
|
|
||||||
"pad"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"pad-end": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"length",
|
|
||||||
"pad"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"lines": {
|
|
||||||
"params": [
|
|
||||||
"str"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"chars": {
|
|
||||||
"params": [
|
|
||||||
"str"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"match": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"regex"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"test?": {
|
|
||||||
"params": [
|
|
||||||
"str",
|
|
||||||
"regex"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dollar: {
|
|
||||||
"args": {
|
|
||||||
"params": []
|
|
||||||
},
|
|
||||||
"argv": {
|
|
||||||
"params": []
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"params": []
|
|
||||||
},
|
|
||||||
"pid": {
|
|
||||||
"params": []
|
|
||||||
},
|
|
||||||
"cwd": {
|
|
||||||
"params": []
|
|
||||||
},
|
|
||||||
"script": {
|
|
||||||
"params": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} as const
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
// Auto-generated by scripts/generate-prelude-metadata.ts
|
|
||||||
// Do not edit manually - run 'bun run generate-prelude-metadata' to regenerate
|
|
||||||
|
|
||||||
export const PRELUDE_NAMES = [
|
|
||||||
"$",
|
|
||||||
"array?",
|
|
||||||
"at",
|
|
||||||
"bnot",
|
|
||||||
"boolean?",
|
|
||||||
"dec",
|
|
||||||
"describe",
|
|
||||||
"dict",
|
|
||||||
"dict?",
|
|
||||||
"each",
|
|
||||||
"echo",
|
|
||||||
"empty?",
|
|
||||||
"exit",
|
|
||||||
"fs",
|
|
||||||
"function?",
|
|
||||||
"identity",
|
|
||||||
"import",
|
|
||||||
"inc",
|
|
||||||
"inspect",
|
|
||||||
"json",
|
|
||||||
"length",
|
|
||||||
"list",
|
|
||||||
"load",
|
|
||||||
"math",
|
|
||||||
"not",
|
|
||||||
"null?",
|
|
||||||
"number?",
|
|
||||||
"range",
|
|
||||||
"ref",
|
|
||||||
"some?",
|
|
||||||
"str",
|
|
||||||
"string?",
|
|
||||||
"type",
|
|
||||||
"var",
|
|
||||||
"var?"
|
|
||||||
] as const
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { parser } from '../../../src/parser/shrimp'
|
import { parser } from '../../../src/parser/shrimp'
|
||||||
import * as Terms from '../../../src/parser/shrimp.terms'
|
import * as Terms from '../../../src/parser/shrimp.terms'
|
||||||
import { SyntaxNode, Tree } from '@lezer/common'
|
import { SyntaxNode } from '@lezer/common'
|
||||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||||
import {
|
import {
|
||||||
SemanticTokensBuilder,
|
SemanticTokensBuilder,
|
||||||
|
|
@ -28,7 +28,9 @@ export const TOKEN_MODIFIERS = [
|
||||||
SemanticTokenModifiers.readonly,
|
SemanticTokenModifiers.readonly,
|
||||||
]
|
]
|
||||||
|
|
||||||
export function buildSemanticTokens(document: TextDocument, tree: Tree): number[] {
|
export function buildSemanticTokens(document: TextDocument): number[] {
|
||||||
|
const text = document.getText()
|
||||||
|
const tree = parser.parse(text)
|
||||||
const builder = new SemanticTokensBuilder()
|
const builder = new SemanticTokensBuilder()
|
||||||
const scopeTracker = new EditorScopeAnalyzer(document)
|
const scopeTracker = new EditorScopeAnalyzer(document)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||||
import { buildDiagnostics } from './diagnostics'
|
import { buildDiagnostics } from './diagnostics'
|
||||||
import { buildSemanticTokens, TOKEN_MODIFIERS, TOKEN_TYPES } from './semanticTokens'
|
import { buildSemanticTokens, TOKEN_MODIFIERS, TOKEN_TYPES } from './semanticTokens'
|
||||||
import { provideCompletions } from './completion/completionProvider'
|
|
||||||
import { provideSignatureHelp } from './signatureHelp'
|
|
||||||
import { PRELUDE_NAMES } from './metadata/prelude-names'
|
|
||||||
import { parser } from '../../../src/parser/shrimp'
|
import { parser } from '../../../src/parser/shrimp'
|
||||||
import { setGlobals } from '../../../src/parser/tokenizer'
|
|
||||||
import { Compiler } from '../../../src/compiler/compiler'
|
import { Compiler } from '../../../src/compiler/compiler'
|
||||||
import { Tree } from '@lezer/common'
|
|
||||||
import {
|
import {
|
||||||
InitializeResult,
|
InitializeResult,
|
||||||
TextDocuments,
|
TextDocuments,
|
||||||
|
|
@ -15,30 +10,19 @@ import {
|
||||||
createConnection,
|
createConnection,
|
||||||
ProposedFeatures,
|
ProposedFeatures,
|
||||||
CompletionItemKind,
|
CompletionItemKind,
|
||||||
TextDocumentChangeEvent,
|
|
||||||
} from 'vscode-languageserver/node'
|
} from 'vscode-languageserver/node'
|
||||||
import { setGlobals } from '../../../src/parser/tokenizer'
|
|
||||||
import { globals } from '../../../src/prelude'
|
|
||||||
|
|
||||||
// Initialize parser with prelude globals so it knows dict/list/str are in scope
|
|
||||||
setGlobals(PRELUDE_NAMES)
|
|
||||||
|
|
||||||
const connection = createConnection(ProposedFeatures.all)
|
const connection = createConnection(ProposedFeatures.all)
|
||||||
const documents = new TextDocuments(TextDocument)
|
const documents = new TextDocuments(TextDocument)
|
||||||
documents.listen(connection)
|
documents.listen(connection)
|
||||||
|
|
||||||
const documentTrees = new Map<string, Tree>()
|
|
||||||
|
|
||||||
// Server capabilities
|
// Server capabilities
|
||||||
connection.onInitialize(handleInitialize)
|
connection.onInitialize(handleInitialize)
|
||||||
|
|
||||||
// Language features
|
// Language features
|
||||||
connection.languages.semanticTokens.on(handleSemanticTokens)
|
connection.languages.semanticTokens.on(handleSemanticTokens)
|
||||||
documents.onDidOpen(handleDocumentOpen)
|
|
||||||
documents.onDidChangeContent(handleDocumentChange)
|
documents.onDidChangeContent(handleDocumentChange)
|
||||||
documents.onDidClose(handleDocumentClose)
|
|
||||||
connection.onCompletion(handleCompletion)
|
connection.onCompletion(handleCompletion)
|
||||||
connection.onSignatureHelp(handleSignatureHelp)
|
|
||||||
|
|
||||||
// Debug commands
|
// Debug commands
|
||||||
connection.onRequest('shrimp/parseTree', handleParseTree)
|
connection.onRequest('shrimp/parseTree', handleParseTree)
|
||||||
|
|
@ -47,7 +31,10 @@ connection.onRequest('shrimp/bytecode', handleBytecode)
|
||||||
// Start listening
|
// Start listening
|
||||||
connection.listen()
|
connection.listen()
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
// Handler implementations
|
// Handler implementations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
function handleInitialize(): InitializeResult {
|
function handleInitialize(): InitializeResult {
|
||||||
connection.console.log('🦐 Server initialized with capabilities')
|
connection.console.log('🦐 Server initialized with capabilities')
|
||||||
const result: InitializeResult = {
|
const result: InitializeResult = {
|
||||||
|
|
@ -56,9 +43,6 @@ function handleInitialize(): InitializeResult {
|
||||||
completionProvider: {
|
completionProvider: {
|
||||||
triggerCharacters: ['.'],
|
triggerCharacters: ['.'],
|
||||||
},
|
},
|
||||||
signatureHelpProvider: {
|
|
||||||
triggerCharacters: [' '],
|
|
||||||
},
|
|
||||||
semanticTokensProvider: {
|
semanticTokensProvider: {
|
||||||
legend: {
|
legend: {
|
||||||
tokenTypes: TOKEN_TYPES,
|
tokenTypes: TOKEN_TYPES,
|
||||||
|
|
@ -72,84 +56,27 @@ function handleInitialize(): InitializeResult {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDocumentOpen(event: TextDocumentChangeEvent<TextDocument>) {
|
|
||||||
const document = event.document
|
|
||||||
setGlobals(Object.keys(globals))
|
|
||||||
const tree = parser.parse(document.getText())
|
|
||||||
documentTrees.set(document.uri, tree)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSemanticTokens(params: any) {
|
function handleSemanticTokens(params: any) {
|
||||||
const document = documents.get(params.textDocument.uri)
|
const document = documents.get(params.textDocument.uri)
|
||||||
if (!document) return { data: [] }
|
if (!document) return { data: [] }
|
||||||
|
|
||||||
const tree = documentTrees.get(params.textDocument.uri)
|
const data = buildSemanticTokens(document)
|
||||||
if (!tree) return { data: [] }
|
|
||||||
|
|
||||||
const data = buildSemanticTokens(document, tree)
|
|
||||||
return { data }
|
return { data }
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDocumentChange(change: TextDocumentChangeEvent<TextDocument>) {
|
function handleDocumentChange(change: any) {
|
||||||
const document = change.document
|
const textDocument = change.document
|
||||||
|
const diagnostics = buildDiagnostics(textDocument)
|
||||||
// Parse and cache
|
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics })
|
||||||
setGlobals(Object.keys(globals))
|
|
||||||
const tree = parser.parse(document.getText())
|
|
||||||
documentTrees.set(document.uri, tree)
|
|
||||||
|
|
||||||
// Build diagnostics using cached tree
|
|
||||||
const diagnostics = buildDiagnostics(document, tree)
|
|
||||||
connection.sendDiagnostics({ uri: document.uri, diagnostics })
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDocumentClose(event: TextDocumentChangeEvent<TextDocument>) {
|
|
||||||
documentTrees.delete(event.document.uri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCompletion(params: any) {
|
function handleCompletion(params: any) {
|
||||||
const document = documents.get(params.textDocument.uri)
|
|
||||||
if (!document) {
|
|
||||||
console.log('❌ No document found')
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const position = params.position
|
|
||||||
const text = document.getText()
|
|
||||||
const offset = document.offsetAt(position)
|
|
||||||
console.log(`📍 Text around cursor: "${text.slice(Math.max(0, offset - 10), offset + 10)}"`)
|
|
||||||
|
|
||||||
// First try context-aware completions (module/dollar)
|
|
||||||
const contextCompletions = provideCompletions(document, position)
|
|
||||||
console.log(`🎯 Context completions count: ${contextCompletions.length}`)
|
|
||||||
if (contextCompletions.length > 0) {
|
|
||||||
console.log(
|
|
||||||
`✅ Returning ${contextCompletions.length} completions:`,
|
|
||||||
contextCompletions.map((c) => c.label).join(', ')
|
|
||||||
)
|
|
||||||
return contextCompletions
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to keywords + prelude globals (for Ctrl+Space in general context)
|
|
||||||
console.log(`⌨️ Falling back to keywords + prelude globals`)
|
|
||||||
const keywords = ['if', 'else', 'do', 'end', 'and', 'or', 'true', 'false', 'null']
|
const keywords = ['if', 'else', 'do', 'end', 'and', 'or', 'true', 'false', 'null']
|
||||||
const keywordCompletions = keywords.map((keyword) => ({
|
|
||||||
|
return keywords.map((keyword) => ({
|
||||||
label: keyword,
|
label: keyword,
|
||||||
kind: CompletionItemKind.Keyword,
|
kind: CompletionItemKind.Keyword,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const preludeCompletions = PRELUDE_NAMES.map((name) => ({
|
|
||||||
label: name,
|
|
||||||
kind: CompletionItemKind.Function,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return [...keywordCompletions, ...preludeCompletions]
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSignatureHelp(params: any) {
|
|
||||||
const document = documents.get(params.textDocument.uri)
|
|
||||||
if (!document) return
|
|
||||||
return provideSignatureHelp(document, params.position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleParseTree(params: { uri: string }) {
|
function handleParseTree(params: { uri: string }) {
|
||||||
|
|
@ -157,13 +84,8 @@ function handleParseTree(params: { uri: string }) {
|
||||||
const document = documents.get(params.uri)
|
const document = documents.get(params.uri)
|
||||||
if (!document) return 'Document not found'
|
if (!document) return 'Document not found'
|
||||||
|
|
||||||
const tree = documentTrees.get(params.uri)
|
|
||||||
if (!tree) {
|
|
||||||
connection.console.error(`🦐 No cached tree for ${params.uri}`)
|
|
||||||
return 'No cached parse tree available'
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = document.getText()
|
const text = document.getText()
|
||||||
|
const tree = parser.parse(text)
|
||||||
const cursor = tree.cursor()
|
const cursor = tree.cursor()
|
||||||
|
|
||||||
let formatted = ''
|
let formatted = ''
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
import { SignatureHelp, SignatureInformation, ParameterInformation } from 'vscode-languageserver/node'
|
|
||||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
|
||||||
import { Tree, SyntaxNode } from '@lezer/common'
|
|
||||||
import { parser } from '../../../src/parser/shrimp'
|
|
||||||
import { completions } from './metadata/prelude-completions'
|
|
||||||
|
|
||||||
export const provideSignatureHelp = (
|
|
||||||
document: TextDocument,
|
|
||||||
position: { line: number; character: number }
|
|
||||||
): SignatureHelp | undefined => {
|
|
||||||
const text = document.getText()
|
|
||||||
const tree = parser.parse(text)
|
|
||||||
const cursorPos = document.offsetAt(position)
|
|
||||||
|
|
||||||
const context = findCallContext(tree, cursorPos, text)
|
|
||||||
if (!context) return
|
|
||||||
|
|
||||||
const params = lookupFunctionParams(context.funcName)
|
|
||||||
if (!params) return
|
|
||||||
|
|
||||||
return {
|
|
||||||
signatures: [buildSignature(context.funcName, params)],
|
|
||||||
activeParameter: Math.min(context.argCount, params.length - 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const findCallContext = (tree: Tree, cursorPos: number, text: string) => {
|
|
||||||
const findBestCall = (node: SyntaxNode): SyntaxNode | undefined => {
|
|
||||||
let result: SyntaxNode | undefined
|
|
||||||
|
|
||||||
const isCall = node.name === 'FunctionCall' || node.name === 'FunctionCallOrIdentifier'
|
|
||||||
|
|
||||||
// Call ends just before cursor (within 5 chars)
|
|
||||||
if (isCall && node.to <= cursorPos && cursorPos <= node.to + 5) {
|
|
||||||
result = node
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cursor is inside the call's span
|
|
||||||
if (isCall && node.from < cursorPos && cursorPos < node.to) {
|
|
||||||
result = node
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse - prefer smaller spans (more specific)
|
|
||||||
let child = node.firstChild
|
|
||||||
while (child) {
|
|
||||||
const found = findBestCall(child)
|
|
||||||
if (found) {
|
|
||||||
const foundSpan = found.to - found.from
|
|
||||||
const resultSpan = result ? result.to - result.from : Infinity
|
|
||||||
if (foundSpan < resultSpan) {
|
|
||||||
result = found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
child = child.nextSibling
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const call = findBestCall(tree.topNode)
|
|
||||||
if (!call) return
|
|
||||||
|
|
||||||
// Count args before cursor
|
|
||||||
let argCount = 0
|
|
||||||
let child = call.firstChild
|
|
||||||
while (child) {
|
|
||||||
if ((child.name === 'PositionalArg' || child.name === 'NamedArg') && child.to <= cursorPos) {
|
|
||||||
argCount++
|
|
||||||
}
|
|
||||||
child = child.nextSibling
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract function name
|
|
||||||
const firstChild = call.firstChild
|
|
||||||
if (!firstChild) return
|
|
||||||
|
|
||||||
let funcName: string | undefined
|
|
||||||
if (firstChild.name === 'DotGet') {
|
|
||||||
funcName = text.slice(firstChild.from, firstChild.to)
|
|
||||||
} else if (firstChild.name === 'Identifier') {
|
|
||||||
funcName = text.slice(firstChild.from, firstChild.to)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!funcName) return
|
|
||||||
|
|
||||||
return { funcName, argCount }
|
|
||||||
}
|
|
||||||
|
|
||||||
const lookupFunctionParams = (funcName: string): string[] | undefined => {
|
|
||||||
// Handle module functions: "list.map" → modules.list.map
|
|
||||||
if (funcName.includes('.')) {
|
|
||||||
const [moduleName, methodName] = funcName.split('.')
|
|
||||||
const module = completions.modules[moduleName as keyof typeof completions.modules]
|
|
||||||
const method = module?.[methodName as keyof typeof module]
|
|
||||||
return method?.params as string[] | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Handle top-level prelude functions (print, range, etc.)
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildSignature = (funcName: string, params: string[]): SignatureInformation => {
|
|
||||||
const label = `${funcName}(${params.join(', ')})`
|
|
||||||
const parameters: ParameterInformation[] = params.map(p => ({ label: p }))
|
|
||||||
return { label, parameters }
|
|
||||||
}
|
|
||||||
BIN
vscode-extension/shrimp-0.0.1.vsix
Normal file
BIN
vscode-extension/shrimp-0.0.1.vsix
Normal file
Binary file not shown.
|
|
@ -1,41 +0,0 @@
|
||||||
import { parser } from '../../src/parser/shrimp'
|
|
||||||
import { setGlobals } from '../../src/parser/tokenizer'
|
|
||||||
import { PRELUDE_NAMES } from '../server/src/prelude-names'
|
|
||||||
|
|
||||||
// Set globals for DotGet detection
|
|
||||||
setGlobals(PRELUDE_NAMES as unknown as string[])
|
|
||||||
|
|
||||||
// Test cases - does incomplete DotGet parse correctly?
|
|
||||||
const testCases = [
|
|
||||||
'dict.',
|
|
||||||
'dict.g',
|
|
||||||
'dict.get',
|
|
||||||
'$.',
|
|
||||||
'$.e',
|
|
||||||
'$.env',
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const code of testCases) {
|
|
||||||
console.log(`\nTesting: "${code}"`)
|
|
||||||
const tree = parser.parse(code)
|
|
||||||
const cursor = tree.cursor()
|
|
||||||
|
|
||||||
// Print the parse tree
|
|
||||||
const printTree = (depth = 0) => {
|
|
||||||
const indent = ' '.repeat(depth)
|
|
||||||
console.log(`${indent}${cursor.name} [${cursor.from}-${cursor.to}]`)
|
|
||||||
|
|
||||||
if (cursor.firstChild()) {
|
|
||||||
do {
|
|
||||||
printTree(depth + 1)
|
|
||||||
} while (cursor.nextSibling())
|
|
||||||
cursor.parent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printTree()
|
|
||||||
|
|
||||||
// Check at cursor position (end of string)
|
|
||||||
const node = tree.resolveInner(code.length, -1)
|
|
||||||
console.log(`Node at end: ${node.name} (type: ${node.type.id})`)
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user