# PR — lsp-base (LSP MVP) **Branch:** `pr-08-lsp-mvp` ## Briefing Queremos um **LSP mínimo funcional** que já permita trabalhar PBS no VSCode com: * erros aparecendo (diagnostics) * navegação básica (goto definition) * visão estrutural (document/workspace symbols) Regras-chave (MVP): * `didChange` será **full-text** * rebuild será **coarse** (recompila o projeto inteiro) sempre que qualquer arquivo muda * sem incremental analysis ainda * comentários extensivos com exemplos se necessário e em inglês sempre Este PR não inclui semantic tokens nem completion (virão nos PRs seguintes). --- ## Alvo (Features) ### LSP endpoints * ✅ `initialize` * ✅ `textDocument/didOpen` * ✅ `textDocument/didChange` (FULL) * ✅ `textDocument/didClose` * ✅ `textDocument/documentSymbol` * ✅ `workspace/symbol` * ✅ `textDocument/definition` * ✅ `textDocument/publishDiagnostics` ### Modelo de build * `AnalysisDb` em memória * `FileDb` (uri → texto) * `rebuild()` recompila **workspace inteiro** e produz um snapshot --- ## Design (como deve funcionar) ### 1) AnalysisDb: estado e snapshot **Objetivo:** separar “estado de arquivos” de “resultado de análise”. Estruturas recomendadas: ```rust pub struct AnalysisDb { pub file_db: FileDb, pub revision: u64, pub active_rebuild: Option, pub last_good: Option, } pub struct AnalysisSnapshot { pub diagnostics: Vec, pub symbols: SymbolIndex, // index por Project/Module (coarse) pub node_to_symbol: NodeToSymbol, // para definition pub def_index: DefIndex, // opcional se você já tiver pub ast: AstArena, // opcional (mas útil para nodes) } ``` > Observação: use os tipos reais do seu compiler. O importante é ter um “snapshot” único que o LSP consulta. ### 2) Fluxo de rebuild * `didOpen/didChange`: * atualizar `file_db` com texto atual * incrementar `revision` * disparar `request_rebuild()` (coalescing) * `request_rebuild()`: * cancela rebuild anterior se houver * agenda um rebuild assíncrono (tokio task) * no fim, se não estiver cancelado e se `revision` não mudou, grava `last_good` e publica diagnostics ### 3) Diagnostics * O compiler já deve produzir diagnostics com `Span { file, start, end }` em bytes. * Para publicar, converter: * `Span` → `lsp_types::Range` via `TextIndex` (já existe do refactor) Regra: * Diagnóstico sem `Span` (ou span inválido) deve ser **ignorado** ou degradado para range 0..0. ### 4) Definition Ao receber `textDocument/definition`: 1. converter `Position` → byte offset (com `TextIndex`) 2. encontrar `NodeId` no ponto 3. resolver `NodeId -> SymbolId` via `node_to_symbol` 4. pegar `Symbol.decl_span` 5. converter `decl_span` → `Location` > Se `NodeId` não existir ou não resolver símbolo: retornar `None`. ### 5) documentSymbol / workspaceSymbol * `documentSymbol`: filtrar símbolos cujo `decl_span.file` == arquivo da request. * `workspaceSymbol`: busca textual simples (contains/case-insensitive) nos nomes de símbolos (coarse) e retorna top N. --- ## Tarefas de implementação (checklist técnico) ### Capabilities em `initialize` Declarar no server: * `textDocumentSync: Full` * `definitionProvider: true` * `documentSymbolProvider: true` * `workspaceSymbolProvider: true` * `diagnosticProvider`: use publishDiagnostics (push) ### didOpen * upsert texto * request_rebuild ### didChange (Full) * receber texto completo do doc * upsert texto * request_rebuild ### didClose * remover do file_db (ou marcar fechado) * publicar diagnostics vazios para o arquivo fechado ### Conversions * `uri <-> FileId`: FileDb precisa mapear URI para FileId estável. * `Span -> Range`: usar `TextIndex` do texto atual do arquivo. * `Position -> byte`: usar `TextIndex`. ### Node lookup Você precisa de uma função no snapshot (ou util) tipo: * `fn node_at(file: FileId, byte: u32) -> Option` MVP aceitável: * se você ainda não tiver um índice de nodes por span, pode: * usar AST arena e fazer busca linear na árvore (aceitável no coarse MVP) --- ## Test Harness (mínimo, mas real) ### Opção A (preferida): tests com `tower-lsp` client in-process Criar um teste `tests/lsp_mvp.rs`: * sobe o backend LSP em memória * envia: * `initialize` * `didOpen` com um fixture PBS (2 arquivos) * espera `publishDiagnostics` (pode interceptar via `Client` mock) * chama `definition` em uma posição de uso e verifica que retorna `Location` do `decl_span` **Aceite:** * definition retorna arquivo correto * range bate com span convertido ### Opção B (mais simples): unit tests nos adaptadores Se o harness LSP demorar, pelo menos criar: * `span_to_range_uses_utf16()` * `position_to_byte_roundtrip()` * `definition_resolves_to_decl_span()` usando snapshot fake. --- ## Checklist de aceite (obrigatório) * [ ] `cargo test -q` passa no workspace * [ ] VSCode: abrir arquivo `.pbs` mostra diagnostics (pelo menos 1 erro sintático) * [ ] VSCode: `Go to Definition` funciona para símbolo resolvido * [ ] VSCode: Outline mostra `documentSymbol` * [ ] Mudanças em um arquivo disparam rebuild e atualizam diagnostics * [ ] Unicode: diagnostics não ficam “desalinhados” (teste manual com `ação`/emoji) --- ## Fora de escopo (explicitamente) * semantic tokens * completion * references/rename * hover/signatureHelp * incremental analysis e cancelation avançada --- # PR — lsp-hightlight-base (Semantic Tokens — lexer-first) **Branch:** `pr-12a-lsp-semantic-lexer` ## Briefing Queremos **highlight no VSCode via LSP**, sem depender de resolver e sem TextMate. Estratégia: * Implementar `textDocument/semanticTokens/full`. * Gerar tokens **lexer-first**: comments/strings/numbers/keywords/identifiers. * Converter spans (bytes) para LSP positions (UTF-16) usando `TextIndex`. * comentários extensivos com exemplos se necessário e em inglês sempre Isso entrega um highlight “bom o suficiente” e muito estável, mesmo com arquivo com erro de parse. --- ## Alvo (Features) * ✅ `textDocument/semanticTokens/full` * ✅ `SemanticTokensLegend` consistente * ✅ tokens derivados do lexer Opcional (não obrigatório neste PR): * `semanticTokens/range` * `semanticTokens/full/delta` --- ## Design ### 1) Legend fixa Escolher um conjunto pequeno de token types: * `comment` * `string` * `number` * `keyword` * `operator` (opcional) * `variable` (para identifiers genéricos) > Não inventar muitos tipos agora; fácil expandir depois. ### 2) Fonte de tokens Implementar uma função no analysis/compiler layer (ou no lsp crate) que, dado: * `FileId` * `text: &str` retorna `Vec<(Span, TokenType, TokenModifiers)>`. **Importante:** * Spans são em bytes. * Devem ser **não sobrepostos** e preferencialmente em ordem. ### 3) Conversão para formato LSP (deltas) LSP semantic tokens usa encoding em deltas: * `deltaLine`, `deltaStartChar`, `length`, `tokenType`, `tokenModifiers` Algoritmo: 1. Converter `Span.start` e `Span.end` em `(line, utf16_col)`. 2. Calcular `length` em UTF-16 units para o trecho (start..end). 3. Ordenar por `(line, col)`. 4. Emitir deltas. Regra: * Se `Span` cruza linhas, **quebrar** em múltiplos tokens por linha (MVP seguro). ### 4) Robustez * Token inválido (end < start, ou fora do texto) deve ser ignorado. * Se o arquivo não estiver no `FileDb`, retornar tokens vazios. --- ## Tarefas de implementação ### Server capabilities No `initialize`, anunciar: * `semanticTokensProvider` com: * `legend` * `full: true` * `range: false` (por enquanto) ### Handler Implementar `semantic_tokens_full(params)`: * pegar `uri` * buscar texto no `file_db` * gerar tokens do lexer * converter com `TextIndex` * retornar `SemanticTokensResult::Tokens` ### Lexer tokens Se você já tem lexer com spans: * mapear tokens para os tipos (keyword/string/comment/number/identifier) * keywords: pode ser por enum do token ou por tabela Se o lexer não marca keyword vs ident: * fallback: parse por string e classifica keywords via `HashSet<&'static str>`. --- ## Testes ### Unit tests (obrigatórios) 1. `semantic_tokens_legend_is_stable()` * garante que legend não muda sem intenção. 2. `semantic_tokens_are_sorted_and_non_negative()` * gera tokens em um fixture com 2 linhas * valida que deltas nunca ficam negativos e que ordem é válida. 3. `semantic_tokens_unicode_length_utf16()` * texto com `ação🙂` * valida que `length` corresponde a UTF-16 (emoji conta como 2). ### Teste manual (aceite) * Abrir `.pbs` no VSCode * Verificar: * strings e comentários coloridos * keywords coloridas * números coloridos * identifiers coloridos (mesmo que genérico) --- ## Checklist de aceite * [ ] `cargo test -q` passa * [ ] VSCode: arquivos PBS ficam coloridos via LSP (sem TextMate) * [ ] Unicode não quebra offsets * [ ] Arquivo com erro de parse ainda tem highlight (lexer-first) --- ## Fora de escopo * semantic tokens semântico (type vs fn vs var) — virá em `PR-12b` * `range`/`delta` * completion --- # PR — lsp-completion-base (Completion mínimo) **Branch:** `pr-11a-lsp-completion-min` ## Briefing Queremos autocomplete **mínimo mas útil** para conseguir escrever SDK/ECS em PBS sem fricção. Princípio: * Não depende de scope facts, nem type facts. * Usa somente: * keywords/builtins * símbolos top-level do módulo atual * exports/imports visíveis no projeto (coarse) Isso é suficiente para começar a programar e evoluir o LSP depois. * comentários extensivos com exemplos se necessário e em inglês sempre --- ## Alvo (Features) * ✅ `textDocument/completion` * ✅ `completionItem/resolve` (opcional; pode retornar item já completo) --- ## Design ### 1) Buckets e ordenação Retornar `CompletionList` com itens nesta prioridade: 1. Keywords (`fn`, `let`, `mutate`, `declare`, `struct`, `storage`, `if`, `else`, `for`, `return`, etc.) 2. Builtins/funções globais (ex.: `alloc`, `box`, `unbox`, `range`, etc. — conforme spec do PBS) 3. Símbolos do módulo atual (top-level) 4. Símbolos “workspace visible” (exports/imports), limitado (ex.: top 200) **Regra de ranking simples:** * locals (não teremos) > módulo atual > workspace ### 2) Contexto Para completion mínimo, só precisamos: * `uri` do documento * `position` (para pegar prefixo) Prefixo: * converter `Position` -> byte * extrair texto até o cursor e identificar o “token parcial” (regex simples `[A-Za-z_][A-Za-z0-9_]*$`) ### 3) Fonte dos símbolos Usar o `AnalysisSnapshot` (produzido na PR-08) para expor: * `fn symbols_in_file(file: FileId) -> Vec` * `fn symbols_in_module(project: ProjectId, module: ModuleId) -> Vec` * `fn workspace_symbols() -> impl Iterator` MVP aceitável: * `workspace_symbols()` pode ser um vetor “flatten” pré-calculado no snapshot. ### 4) CompletionItem Mapeamento para `CompletionItemKind`: * functions -> `FUNCTION` * types -> `CLASS` (ou `STRUCT` se quiser) * modules -> `MODULE` * variables/constants -> `VARIABLE` / `CONSTANT` Campos: * `label`: nome * `detail`: (opcional) `"fn"/"type"/"module"` * `sortText`: prefixado para impor bucket (ex.: `"1_"`, `"2_"`) * `filterText`: label --- ## Tarefas de implementação ### Capabilities No `initialize`: * `completionProvider: { resolveProvider: false, triggerCharacters: [".", ":"]? }` > Para completion mínimo, nem precisa trigger chars. Pode deixar default. ### Handler `completion` * buscar texto e `TextIndex` * calcular prefixo * montar lista de candidatos por bucket * filtrar por prefixo (case-sensitive ou insensitive; escolha e documente) * limitar tamanho * retornar `CompletionResponse::List` ### Builtins/keywords Criar tabelas estáticas em `prometeu-lsp`: * `static KEYWORDS: &[&str] = ...` * `static BUILTINS: &[&str] = ...` --- ## Testes ### Unit tests (obrigatórios) 1. `completion_extracts_prefix()` * valida regex de prefixo 2. `completion_includes_keywords()` * chama handler com texto vazio e posição 0 * garante que `fn` aparece 3. `completion_filters_by_prefix()` * prefixo `"ra"` deve sugerir `range` (se builtin) 4. (se possível) `completion_includes_module_symbols()` * usando snapshot fake ou fixture compilado pela infra da PR-08 ### Teste manual (aceite) * Abrir arquivo PBS e digitar `ra` -> aparece `range` * Digitar nome de tipo/fn do SDK -> aparece suggestion --- ## Checklist de aceite * [ ] `cargo test -q` passa * [ ] VSCode: autocomplete sugere keywords e builtins * [ ] VSCode: autocomplete sugere símbolos do módulo atual * [ ] Não trava com rebuild coarse --- ## Fora de escopo * locals em scope * members (`obj.`) * signatureHelp * hover