From 028ccf2bf9861373d23a5a0b2dacf4ab9b97000b Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Mon, 24 Nov 2025 12:20:54 -0800 Subject: [PATCH] Delete 2025-01-24-autocomplete-design.md --- .../plans/2025-01-24-autocomplete-design.md | 345 ------------------ 1 file changed, 345 deletions(-) delete mode 100644 vscode-extension/docs/plans/2025-01-24-autocomplete-design.md diff --git a/vscode-extension/docs/plans/2025-01-24-autocomplete-design.md b/vscode-extension/docs/plans/2025-01-24-autocomplete-design.md deleted file mode 100644 index ea3b05e..0000000 --- a/vscode-extension/docs/plans/2025-01-24-autocomplete-design.md +++ /dev/null @@ -1,345 +0,0 @@ -# Autocomplete Design for Shrimp VSCode Extension - -**Date:** 2025-01-24 -**Status:** Approved, ready for implementation - -## Goal - -Add intelligent autocomplete for Shrimp prelude functions and dollar properties in the VSCode extension. - -## Scope - -### Phase 1 (This Implementation) -- **Module completions:** `dict.*`, `list.*`, `str.*` member functions -- **Dollar completions:** `$.*` runtime properties -- **Trigger behavior:** - - Auto-trigger on `.` for module/dollar members - - Manual trigger (Ctrl+Space) for keywords (existing behavior) - -### Future Phases (Deferred) -- Top-level prelude globals (echo, type, inspect, etc.) -- Completion descriptions and documentation -- Chained property access (e.g., `dict.keys.`) - -## Requirements - -1. Show function signatures with parameter names (e.g., `get(dict, key)`) -2. Use proper VSCode completion kinds (Method for functions, Property for dollar) -3. Parser-based context detection (no regex hacks) -4. TypeScript metadata for easy maintenance and editor support -5. Minimal, readable code - -## Architecture - -### File Structure - -``` -vscode-extension/ -├── server/src/ -│ ├── metadata/ -│ │ └── prelude-completions.ts # TypeScript metadata -│ ├── completion/ -│ │ ├── completionProvider.ts # Main completion logic -│ │ └── contextAnalyzer.ts # Parser-based context detection -│ └── server.ts # Wire up completion handler -``` - -### Data Flow - -1. User types `dict.` → VSCode sends `onCompletion` request with cursor position -2. `contextAnalyzer` uses Shrimp parser to determine context: - - Is cursor in a DotGet node? - - What's on the left side of the dot? -3. `completionProvider` returns appropriate completions based on context: - - Module functions if `dict.`, `list.`, or `str.` - - Dollar properties if `$.` - - Empty list otherwise (fall back to keywords) - -## Parser Behavior Analysis - -### Test Results - -We tested how incomplete and partial DotGet expressions parse: - -| Input | Parse Result | Node at cursor | -|------------|-----------------------------------------|----------------| -| `dict.` | `DotGet(IdentifierBeforeDot, ⚠)` | `DotGet` | -| `dict.g` | `DotGet(IdentifierBeforeDot, Identifier)` | `Identifier` | -| `dict.get` | `DotGet(IdentifierBeforeDot, Identifier)` | `Identifier` | -| `$.` | `DotGet(Dollar, ⚠)` | `DotGet` | -| `$.e` | `DotGet(Dollar, Identifier)` | `Identifier` | -| `$.env` | `DotGet(Dollar, Identifier)` | `Identifier` | - -**Key Insights:** -- Incomplete DotGet (`dict.`, `$.`) → `resolveInner` returns `DotGet` node directly -- Partial identifier (`dict.g`, `$.e`) → `resolveInner` returns `Identifier` node (parent is `DotGet`) -- Error node (⚠) marks incomplete input, perfect for triggering autocomplete - -### Context Detection Strategy - -Handle both cases: -1. **Node type is `DotGet`** → cursor right after dot -2. **Node type is `Identifier` with `DotGet` parent** → typing property name - -Extract left side of dot to determine if it's a known module or `$`. - -## Implementation Details - -### 1. Metadata Structure - -**File:** `server/src/metadata/prelude-completions.ts` - -```typescript -export type CompletionMetadata = { - params: string[] - description?: string // Optional for now, add later -} - -export const completions = { - modules: { - dict: { - 'get': { params: ['dict', 'key'] }, - 'set': { params: ['dict', 'key', 'value'] }, - 'keys': { params: ['dict'] }, - 'values': { params: ['dict'] }, - 'has?': { params: ['dict', 'key'] }, - }, - list: { - 'map': { params: ['list', 'fn'] }, - 'filter': { params: ['list', 'fn'] }, - 'reduce': { params: ['list', 'fn', 'initial'] }, - 'reverse': { params: ['list'] }, - 'length': { params: ['list'] }, - }, - str: { - 'split': { params: ['str', 'separator'] }, - 'join': { params: ['list', 'separator'] }, - 'trim': { params: ['str'] }, - 'uppercase': { params: ['str'] }, - 'lowercase': { params: ['str'] }, - }, - }, - dollar: { - 'env': { params: [] }, - 'pid': { params: [] }, - 'args': { params: [] }, - 'argv': { params: [] }, - 'cwd': { params: [] }, - 'script': { params: [] }, - }, -} as const -``` - -**Design decisions:** -- TypeScript (not JSON) for editor autocomplete while editing metadata -- `as const` for type safety -- Single export with nested structure (`completions.modules`, `completions.dollar`) -- Start with ~20 most common functions per module - -### 2. Context Analyzer - -**File:** `server/src/completion/contextAnalyzer.ts` - -```typescript -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' } - -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 - let node = tree.resolveInner(offset, -1) - - // Case 1: Incomplete DotGet (dict. or $.) - // resolveInner returns DotGet node directly - if (node.type.id === Terms.DotGet) { - const leftSide = extractLeftSide(node, text) - if (leftSide === '$') return { type: 'dollar' } - if (['dict', 'list', 'str'].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) - if (leftSide === '$') return { type: 'dollar' } - if (['dict', 'list', 'str'].includes(leftSide)) { - return { type: 'module', moduleName: leftSide } - } - } - - return { type: 'none' } -} - -const extractLeftSide = (dotGetNode: SyntaxNode, text: string): string => { - const firstChild = dotGetNode.firstChild - if (!firstChild) return '' - return text.slice(firstChild.from, firstChild.to) -} -``` - -**Design decisions:** -- Use Shrimp parser (not regex) for accurate token detection -- Return typed context for type-safe consumption -- Handle both incomplete and partial completion cases -- Simple left-side extraction from first child of DotGet - -### 3. Completion Provider - -**File:** `server/src/completion/completionProvider.ts` - -```typescript -import { CompletionItem, CompletionItemKind } from 'vscode-languageserver/node' -import { TextDocument } from 'vscode-languageserver-textdocument' -import { completions } from '../metadata/prelude-completions' -import { analyzeCompletionContext } from './contextAnalyzer' - -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 -} - -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, - })) -} - -const buildDollarCompletions = (): CompletionItem[] => { - return Object.entries(completions.dollar).map(([name, meta]) => ({ - label: name, - kind: CompletionItemKind.Property, - insertText: name, - })) -} -``` - -**Design decisions:** -- `CompletionItemKind.Method` for module functions (shows function icon) -- `CompletionItemKind.Property` for dollar properties (shows property icon) -- `detail` field shows parameter signature -- Simple and focused - easy to extend later with descriptions - -### 4. Server Integration - -**File:** `server/src/server.ts` (updates) - -**Update initialization to declare trigger character:** - -```typescript -function handleInitialize(): InitializeResult { - connection.console.log('🦐 Server initialized with capabilities') - return { - capabilities: { - textDocumentSync: TextDocumentSyncKind.Full, - completionProvider: { - triggerCharacters: ['.'], // Auto-trigger on dot - }, - semanticTokensProvider: { /* ... existing ... */ }, - }, - } -} -``` - -**Update completion handler:** - -```typescript -import { provideCompletions } from './completion/completionProvider' - -function handleCompletion(params: any) { - const document = documents.get(params.textDocument.uri) - if (!document) return [] - - const position = params.position - - // First try context-aware completions (module/dollar) - const contextCompletions = provideCompletions(document, position) - if (contextCompletions.length > 0) { - return contextCompletions - } - - // Fall back to keywords (for Ctrl+Space in general context) - const keywords = ['if', 'else', 'do', 'end', 'and', 'or', 'true', 'false', 'null'] - return keywords.map((keyword) => ({ - label: keyword, - kind: CompletionItemKind.Keyword, - })) -} -``` - -**Design decisions:** -- Try context completions first (module/dollar) -- Fall back to keywords if no context match -- Preserves existing keyword completion behavior -- Simple, linear flow - -## Testing Strategy - -### Manual Testing -1. Type `dict.` → should show dict completions -2. Type `list.m` → should filter to `map` -3. Type `$.` → should show dollar properties -4. Type random identifier + `.` → should show nothing (fall back to keywords) -5. Verify signatures appear in completion detail - -### Future Automated Testing -- Unit tests for context analyzer with various parse trees -- Unit tests for completion provider with mock contexts -- Integration tests for end-to-end completion flow - -## Future Enhancements - -### Phase 2: Documentation -- Add `description` field to metadata -- Show in completion item documentation -- Consider pulling from actual prelude JSDoc comments - -### Phase 3: Top-Level Completions -- Show prelude globals on Ctrl+Space -- Requires different trigger logic (not just dot) -- May need position-aware filtering (don't show in strings/comments) - -### Phase 4: Advanced Features -- Chained completions (`dict.keys.`) -- Type-aware completions (know what `dict.keys` returns) -- Snippet completions with parameter placeholders -- Fuzzy matching for better search - -## References - -- [VSCode Language Server Guide](https://code.visualstudio.com/api/language-extensions/language-server-extension-guide) -- [CompletionItemKind Documentation](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind) -- Shrimp Grammar: `src/parser/shrimp.grammar` (lines 245-248 for DotGet definition)