feat(lsp): add textDocument/rangeFormatting support
CI / Test (push) Successful in 50s
CI / Build snapshot (push) Failing after 1m12s

Advertise DocumentRangeFormattingProvider in server capabilities and
implement rangeFormat: formats the complete document, then returns an
edit covering only the minimal changed-line region that overlaps the
client's selection. Also includes gofmt alignment fixes across lint
and format packages.
This commit is contained in:
2026-06-28 16:28:27 +02:00
parent a98dee1877
commit d1150a7eea
6 changed files with 168 additions and 23 deletions
+70
View File
@@ -156,6 +156,76 @@ func TestFormatting(t *testing.T) {
}
}
func TestRangeFormatting(t *testing.T) {
// Two statements: first is unformatted, second is already formatted.
// Range request covers only the first statement (line 0).
sql := "select a, b from t where x = 1;\nSELECT z\nFROM s;\n"
uri := "file:///range.sql"
var input []byte
input = append(input, frame(1, "initialize", map[string]interface{}{})...)
input = append(input, notifFrame("initialized", nil)...)
input = append(input, notifFrame("textDocument/didOpen", map[string]interface{}{
"textDocument": map[string]interface{}{
"uri": uri, "languageId": "sql", "version": 1, "text": sql,
},
})...)
input = append(input, frame(2, "textDocument/rangeFormatting", map[string]interface{}{
"textDocument": map[string]interface{}{"uri": uri},
"range": map[string]interface{}{
"start": map[string]interface{}{"line": 0, "character": 0},
"end": map[string]interface{}{"line": 0, "character": 31},
},
"options": map[string]interface{}{"tabSize": 2, "insertSpaces": true},
})...)
input = append(input, frame(3, "shutdown", nil)...)
input = append(input, notifFrame("exit", nil)...)
out := runServer(t, input)
var resp map[string]interface{}
for i := 0; i < 10; i++ {
r := readResp(t, out)
if r == nil {
break
}
if id, ok := r["id"]; ok && id.(float64) == 2 {
resp = r
break
}
}
if resp == nil {
t.Fatal("no response for rangeFormatting request")
}
result, ok := resp["result"].([]interface{})
if !ok || len(result) == 0 {
t.Fatalf("expected non-empty edit array, got %v", resp["result"])
}
edit := result[0].(map[string]interface{})
newText, _ := edit["newText"].(string)
if !strings.Contains(newText, "SELECT") {
t.Errorf("expected formatted SELECT in edit, got: %q", newText)
}
if strings.Contains(newText, "SELECT z") {
t.Errorf("range formatting edited second statement unexpectedly; got: %q", newText)
}
}
func TestInitialize_AdvertisesRangeFormatting(t *testing.T) {
input := append(frame(1, "initialize", map[string]interface{}{}),
notifFrame("initialized", map[string]interface{}{})...)
input = append(input, frame(2, "shutdown", nil)...)
input = append(input, notifFrame("exit", nil)...)
out := runServer(t, input)
resp := readResp(t, out)
caps := resp["result"].(map[string]interface{})["capabilities"].(map[string]interface{})
if caps["documentRangeFormattingProvider"] != true {
t.Error("expected documentRangeFormattingProvider=true")
}
}
func TestDidClose_ClearsDiagnostics(t *testing.T) {
uri := "file:///test.sql"
// SQL with a lint violation (SELECT * triggers COR001)