prometeu-studio/docs/roadmaps/lsp/LSP - base PRs.md
2026-03-24 13:42:18 +00:00

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):

  • 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:

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_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:

    • Spanlsp_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_spanLocation

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<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:

    • 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.
  1. 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.
  1. 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<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 (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
  1. completion_includes_keywords()
  • chama handler com texto vazio e posição 0
  • garante que fn aparece
  1. completion_filters_by_prefix()
  • prefixo "ra" deve sugerir range (se builtin)
  1. (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