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

    * `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<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.

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

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
