13 KiB
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):
didChangeserá 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
AnalysisDbem memóriaFileDb(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:
pub struct AnalysisDb {
pub file_db: FileDb,
pub revision: u64,
pub active_rebuild: Option<RebuildHandle>,
pub last_good: Option<AnalysisSnapshot>,
}
pub struct AnalysisSnapshot {
pub diagnostics: Vec<Diagnostic>,
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_dbcom texto atual - incrementar
revision - disparar
request_rebuild()(coalescing)
- atualizar
-
request_rebuild():- cancela rebuild anterior se houver
- agenda um rebuild assíncrono (tokio task)
- no fim, se não estiver cancelado e se
revisionnão mudou, gravalast_goode publica diagnostics
3) Diagnostics
-
O compiler já deve produzir diagnostics com
Span { file, start, end }em bytes. -
Para publicar, converter:
Span→lsp_types::RangeviaTextIndex(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:
- converter
Position→ byte offset (comTextIndex) - encontrar
NodeIdno ponto - resolver
NodeId -> SymbolIdvianode_to_symbol - pegar
Symbol.decl_span - converter
decl_span→Location
Se
NodeIdnão existir ou não resolver símbolo: retornarNone.
5) documentSymbol / workspaceSymbol
documentSymbol: filtrar símbolos cujodecl_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: FulldefinitionProvider: truedocumentSymbolProvider: trueworkspaceSymbolProvider: truediagnosticProvider: 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: usarTextIndexdo texto atual do arquivo.Position -> byte: usarTextIndex.
Node lookup
Você precisa de uma função no snapshot (ou util) tipo:
fn node_at(file: FileId, byte: u32) -> Option<NodeId>
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:
initializedidOpencom um fixture PBS (2 arquivos)- espera
publishDiagnostics(pode interceptar viaClientmock) - chama
definitionem uma posição de uso e verifica que retornaLocationdodecl_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 -qpassa no workspace- VSCode: abrir arquivo
.pbsmostra diagnostics (pelo menos 1 erro sintático) - VSCode:
Go to Definitionfunciona 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 - ✅
SemanticTokensLegendconsistente - ✅ tokens derivados do lexer
Opcional (não obrigatório neste PR):
semanticTokens/rangesemanticTokens/full/delta
Design
1) Legend fixa
Escolher um conjunto pequeno de token types:
commentstringnumberkeywordoperator(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:
FileIdtext: &strretornaVec<(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:
- Converter
Span.starteSpan.endem(line, utf16_col). - Calcular
lengthem UTF-16 units para o trecho (start..end). - Ordenar por
(line, col). - Emitir deltas.
Regra:
- Se
Spancruza 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:
-
semanticTokensProvidercom:legendfull: truerange: 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)
semantic_tokens_legend_is_stable()
- garante que legend não muda sem intenção.
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.
semantic_tokens_unicode_length_utf16()
- texto com
ação🙂 - valida que
lengthcorresponde a UTF-16 (emoji conta como 2).
Teste manual (aceite)
-
Abrir
.pbsno VSCode -
Verificar:
- strings e comentários coloridos
- keywords coloridas
- números coloridos
- identifiers coloridos (mesmo que genérico)
Checklist de aceite
cargo test -qpassa- 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:
- Keywords (
fn,let,mutate,declare,struct,storage,if,else,for,return, etc.) - Builtins/funções globais (ex.:
alloc,box,unbox,range, etc. — conforme spec do PBS) - Símbolos do módulo atual (top-level)
- 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:
urido documentoposition(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<SymbolId>fn symbols_in_module(project: ProjectId, module: ModuleId) -> Vec<SymbolId>fn workspace_symbols() -> impl Iterator<Item = (name, kind, decl_span)>
MVP aceitável:
workspace_symbols()pode ser um vetor “flatten” pré-calculado no snapshot.
4) CompletionItem
Mapeamento para CompletionItemKind:
- functions ->
FUNCTION - types ->
CLASS(ouSTRUCTse quiser) - modules ->
MODULE - variables/constants ->
VARIABLE/CONSTANT
Campos:
label: nomedetail: (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)
completion_extracts_prefix()
- valida regex de prefixo
completion_includes_keywords()
- chama handler com texto vazio e posição 0
- garante que
fnaparece
completion_filters_by_prefix()
- prefixo
"ra"deve sugerirrange(se builtin)
- (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-> aparecerange - Digitar nome de tipo/fn do SDK -> aparece suggestion
Checklist de aceite
cargo test -qpassa- 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