# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s # RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s # Tests InsertReplaceEdit ranges when insertReplaceSupport is true. # insert range = [token_start, cursor), replace range = [token_start, token_end). { "jsonrpc": "2.0", "id": 0, "method": "initialize", "params": { "processId": 123, "rootPath": "clangd", "capabilities": { "textDocument": { "completion": { "completionItem": { "insertReplaceSupport": true } } } }, "trace": "off" } } --- # Case 1 (cursor at end of token): "S().a^" # Prefix typed, nothing after cursor. insert == replace. # insert: [4,5], replace: [4,5] {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct S { int a; int abc; int chrząszcz; };\nint main() {\nS().a;\n}"}}} --- {"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":5}}} # CHECK: "id": 1 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK: "filterText": "a", # CHECK-NEXT: "insertText": "a", # CHECK: "textEdit": { # CHECK-NEXT: "insert": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 5, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "newText": "a", # CHECK-NEXT: "replace": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 5, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } --- # Case 2 (mid-word cursor): "S().ab^c" # Cursor mid-word. Replace range extends past cursor to cover suffix. # insert: [4,6], replace: [4,7] {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp","version":2},"contentChanges":[{"text":"struct S { int a; int abc; int chrząszcz; };\nint main() {\nS().abc;\n}"}]}} --- {"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":6}}} # CHECK: "id": 2 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK: "filterText": "abc", # CHECK-NEXT: "insertText": "abc", # CHECK: "textEdit": { # CHECK-NEXT: "insert": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 6, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "newText": "abc", # CHECK-NEXT: "replace": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 7, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } --- # Case 3 (no prefix, word after cursor): "S().^xyz" # Cursor right after dot, entire existing word is suffix. Insert range is empty. # insert: [4,4], replace: [4,7] {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp","version":3},"contentChanges":[{"text":"struct S { int a; int abc; int chrząszcz; };\nint main() {\nS().xyz;\n}"}]}} --- {"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":4}}} # CHECK: "id": 3 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK: "textEdit": { # CHECK-NEXT: "insert": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "newText": {{.*}}, # CHECK-NEXT: "replace": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 7, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } --- # Case 4 (unicode mid-word): "S().chrz^ąszcz" # Cursor mid-word with multi-byte suffix. Tests that ranges handle unicode. # "chrząszcz" is 9 characters starting at column 4. # insert: [4,8], replace: [4,13] {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp","version":4},"contentChanges":[{"text":"struct S { int a; int abc; int chrząszcz; };\nint main() {\nS().chrząszcz;\n}"}]}} --- {"jsonrpc":"2.0","id":4,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":8}}} # CHECK: "id": 4 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK: "filterText": "chrząszcz", # CHECK-NEXT: "insertText": "chrząszcz", # CHECK: "textEdit": { # CHECK-NEXT: "insert": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 8, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "newText": "chrząszcz", # CHECK-NEXT: "replace": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 13, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } --- # Case 5 (replace stops at paren): "S().ab^c(123)" # Replace range covers only the identifier, not the '(' or arguments. # insert: [4,6], replace: [4,7] {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp","version":5},"contentChanges":[{"text":"struct S { int a; int abc; int chrząszcz; };\nint main() {\nS().abc(123);\n}"}]}} --- {"jsonrpc":"2.0","id":5,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":6}}} # CHECK: "id": 5 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK: "filterText": "abc", # CHECK-NEXT: "insertText": "abc", # CHECK: "textEdit": { # CHECK-NEXT: "insert": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 6, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "newText": "abc", # CHECK-NEXT: "replace": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 7, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } --- # Case 6 (replace stops at angle bracket): "S().ab^c()" # Replace range covers only the identifier, not '<' or template arguments. # insert: [4,6], replace: [4,7] {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp","version":6},"contentChanges":[{"text":"struct S { int a; int abc; int chrząszcz; };\nint main() {\nS().abc();\n}"}]}} --- {"jsonrpc":"2.0","id":6,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":6}}} # CHECK: "id": 6 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK: "filterText": "abc", # CHECK-NEXT: "insertText": "abc", # CHECK: "textEdit": { # CHECK-NEXT: "insert": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 6, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "newText": "abc", # CHECK-NEXT: "replace": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 7, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } --- # Case 7 (replace stops before '='): "S().ab^c = 1;" # Replace range covers only the identifier, not the space or '='. # insert: [4,6], replace: [4,7] {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp","version":7},"contentChanges":[{"text":"struct S { int a; int abc; int chrząszcz; };\nint main() {\nS().abc = 1;\n}"}]}} --- {"jsonrpc":"2.0","id":7,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":6}}} # CHECK: "id": 7 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK: "filterText": "abc", # CHECK-NEXT: "insertText": "abc", # CHECK: "textEdit": { # CHECK-NEXT: "insert": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 6, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "newText": "abc", # CHECK-NEXT: "replace": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": 7, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: "start": { # CHECK-NEXT: "character": 4, # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":8,"method":"shutdown"} --- {"jsonrpc":"2.0","method":"exit"}