2020-06-30 13:46:43 +01:00
const std = @import ( " std " ) ;
const headerPkg = @import ( " header " ) ;
2021-10-07 12:34:14 +01:00
const builtin = @import ( " builtin " ) ;
2020-06-30 13:46:43 +01:00
2021-10-07 12:34:14 +01:00
const suffix = if ( builtin . os . tag = = . windows ) " .exe " else " " ;
2020-06-30 13:46:43 +01:00
const allocator = std . heap . page_allocator ;
2021-04-07 14:10:18 +01:00
const initialize_msg =
\\{"processId":6896,"clientInfo":{"name":"vscode","version":"1.46.1"},"rootPath":null,"rootUri":null,"capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional"},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"complexDiagnosticCodeSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true}},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]}},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["comment","keyword","number","regexp","operator","namespace","type","struct","class","interface","enum","typeParameter","function","member","macro","variable","parameter","property","label"],"tokenModifiers":["declaration","documentation","static","abstract","deprecated","readonly"]}},"window":{"workDoneProgress":true}},"trace":"off","workspaceFolders":[{"uri":"file://./tests", "name":"root"}]}
2020-06-30 13:46:43 +01:00
;
2021-04-07 14:10:18 +01:00
const initialize_msg_offs =
\\{"processId":6896,"clientInfo":{"name":"vscode","version":"1.46.1"},"rootPath":null,"rootUri":null,"capabilities":{"offsetEncoding":["utf-16", "utf-8"],"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional"},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"complexDiagnosticCodeSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true}},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]}},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["comment","keyword","number","regexp","operator","namespace","type","struct","class","interface","enum","typeParameter","function","member","macro","variable","parameter","property","label"],"tokenModifiers":["declaration","documentation","static","abstract","deprecated","readonly"]}},"window":{"workDoneProgress":true}},"trace":"off","workspaceFolders":null}
2020-06-30 13:46:43 +01:00
;
2021-04-07 14:10:18 +01:00
const Server = struct {
2022-05-29 18:18:57 +01:00
process : std . ChildProcess ,
2021-04-07 14:10:18 +01:00
request_id : u32 = 1 ,
2020-06-30 13:46:43 +01:00
2021-04-07 14:10:18 +01:00
fn start ( initialization : [ ] const u8 , expect : ? [ ] const u8 ) ! Server {
var server = Server { . process = try startZls ( ) } ;
2020-06-30 13:46:43 +01:00
2021-04-07 14:10:18 +01:00
try server . request ( " initialize " , initialization , expect ) ;
try server . request ( " initialized " , " {} " , null ) ;
return server ;
}
fn request (
self : * Server ,
method : [ ] const u8 ,
params : [ ] const u8 ,
expect : ? [ ] const u8 ,
) ! void {
self . request_id + = 1 ;
const req = try std . fmt . allocPrint ( allocator ,
\\{{"jsonrpc":"2.0","id":{},"method":"{s}","params":{s}}}
, . { self . request_id , method , params } ) ;
const to_server = self . process . stdin . ? . writer ( ) ;
try to_server . print ( " Content-Length: {} \r \n \r \n " , . { req . len } ) ;
try to_server . writeAll ( req ) ;
const expected = expect orelse return ;
var from_server = self . process . stdout . ? . reader ( ) ;
while ( true ) {
const header = headerPkg . readRequestHeader ( allocator , from_server ) catch | err | {
switch ( err ) {
error . EndOfStream = > break ,
else = > return err ,
}
} ;
2021-05-03 13:46:24 +01:00
2021-04-07 14:10:18 +01:00
defer header . deinit ( allocator ) ;
2021-05-03 13:46:24 +01:00
var response_bytes = try allocator . alloc ( u8 , header . content_length ) ;
defer allocator . free ( response_bytes ) ;
if ( ( try from_server . readAll ( response_bytes ) ) ! = header . content_length ) {
2021-04-07 14:10:18 +01:00
return error . InvalidResponse ;
2020-06-30 13:46:43 +01:00
}
2021-04-07 14:10:18 +01:00
// std.debug.print("{s}\n", .{resonse_bytes});
2020-06-30 13:46:43 +01:00
2021-04-07 14:10:18 +01:00
const json_fmt = " { \" jsonrpc \" : \" 2.0 \" , \" id \" : " ;
2021-05-03 13:46:24 +01:00
if ( ! std . mem . startsWith ( u8 , response_bytes , json_fmt ) ) {
try extractError ( response_bytes ) ;
2021-04-07 14:10:18 +01:00
continue ;
}
2020-06-30 13:46:43 +01:00
2021-05-03 13:46:24 +01:00
const rest = response_bytes [ json_fmt . len . . ] ;
2021-04-07 14:10:18 +01:00
const id_end = std . mem . indexOfScalar ( u8 , rest , ',' ) orelse return error . InvalidResponse ;
const id = try std . fmt . parseInt ( u32 , rest [ 0 . . id_end ] , 10 ) ;
if ( id ! = self . request_id ) {
continue ;
}
const result = " , \" result \" : " ;
const msg = rest [ id_end + result . len . . rest . len - 1 ] ;
if ( std . mem . eql ( u8 , msg , expected ) ) {
return ;
} else {
const mismatch = std . mem . indexOfDiff ( u8 , expected , msg ) orelse 0 ;
std . debug . print ( " ==> Expected: \n {s} \n ==> Got: (Mismatch in position {}) \n {s} \n " , . { expected , mismatch , msg } ) ;
return error . InvalidResponse ;
2020-07-03 11:56:57 +01:00
}
}
2020-06-30 13:46:43 +01:00
}
2021-04-07 14:10:18 +01:00
fn extractError ( msg : [ ] const u8 ) ! void {
const log_request =
\\"method":"window/logMessage","params":{"type":
;
if ( std . mem . indexOf ( u8 , msg , log_request ) ) | log_msg | {
const rest = msg [ log_msg + log_request . len . . ] ;
const level = rest [ 0 ] ;
if ( level < = '2' ) {
std . debug . print ( " {s} \n " , . { rest [ 13 . . rest . len - 3 ] } ) ;
if ( level < = '1' ) {
return error . ServerError ;
}
}
2020-07-03 11:56:57 +01:00
}
}
2020-06-30 13:46:43 +01:00
2021-04-07 14:10:18 +01:00
fn shutdown ( self : * Server ) void {
2021-12-30 06:23:01 +00:00
// FIXME this shutdown request fails with a broken pipe on stdin on the CI
2021-12-30 06:44:24 +00:00
self . request ( " shutdown " , " {} " , null ) catch @panic ( " Could not send shutdown request " ) ;
2021-12-30 06:47:12 +00:00
// waitNoError(self.process) catch @panic("Server error");
2021-04-07 14:10:18 +01:00
}
} ;
2021-12-30 06:23:01 +00:00
2022-05-29 18:18:57 +01:00
fn startZls ( ) ! std . ChildProcess {
var process = std . ChildProcess . init ( & [ _ ] [ ] const u8 { " zig-out/bin/zls " + + suffix } , allocator ) ;
2020-06-30 13:46:43 +01:00
process . stdin_behavior = . Pipe ;
2021-12-30 06:47:12 +00:00
process . stdout_behavior = . Pipe ;
2021-12-30 06:53:30 +00:00
process . stderr_behavior = . Inherit ;
2020-06-30 13:46:43 +01:00
process . spawn ( ) catch | err | {
2021-04-07 14:10:18 +01:00
std . debug . print ( " Failed to spawn zls process, error: {} \n " , . { err } ) ;
2020-06-30 13:46:43 +01:00
return err ;
} ;
2020-07-02 12:03:24 +01:00
return process ;
}
2021-12-30 06:23:01 +00:00
2020-07-02 12:03:24 +01:00
fn waitNoError ( process : * std . ChildProcess ) ! void {
2021-04-07 14:10:18 +01:00
const stderr = std . io . getStdErr ( ) . writer ( ) ;
const err_in = process . stderr . ? . reader ( ) ;
var buf : [ 4096 ] u8 = undefined ;
while ( true ) {
const line = err_in . readUntilDelimiterOrEof ( & buf , '\n' ) catch | err | switch ( err ) {
error . StreamTooLong = > {
std . debug . print ( " skipping very long line \n " , . { } ) ;
continue ;
} ,
else = > return err ,
} orelse break ;
if ( std . mem . startsWith ( u8 , line , " [debug " ) ) continue ;
try stderr . writeAll ( line ) ;
try stderr . writeByte ( '\n' ) ;
}
2020-07-02 12:03:24 +01:00
const result = try process . wait ( ) ;
2021-04-07 14:10:18 +01:00
2020-07-02 12:03:24 +01:00
switch ( result ) {
. Exited = > | code | if ( code = = 0 ) {
return ;
} ,
else = > { } ,
}
return error . ShutdownWithError ;
}
test " Open file, ask for semantic tokens " {
2021-04-07 14:10:18 +01:00
var server = try Server . start ( initialize_msg , null ) ;
2021-12-30 05:56:17 +00:00
defer server . shutdown ( ) ;
2021-04-07 14:10:18 +01:00
try server . request ( " textDocument/didOpen " ,
2022-05-29 18:45:08 +01:00
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");"}}
2021-04-07 14:10:18 +01:00
, null ) ;
try server . request ( " textDocument/semanticTokens/full " ,
2022-05-29 18:45:08 +01:00
\\{"textDocument":{"uri":"file:///test.zig"}}
2021-04-07 14:10:18 +01:00
,
\\{"data":[0,0,5,7,0,0,6,3,0,33,0,4,1,11,0,0,2,7,12,0,0,8,5,9,0]}
) ;
2020-07-02 12:03:24 +01:00
}
2020-06-30 13:46:43 +01:00
2021-04-07 14:10:18 +01:00
test " Request completion in an empty file " {
var server = try Server . start ( initialize_msg , null ) ;
2021-12-30 05:56:17 +00:00
defer server . shutdown ( ) ;
2020-06-30 13:46:43 +01:00
2021-04-07 14:10:18 +01:00
try server . request ( " textDocument/didOpen " ,
2021-03-10 08:29:25 +00:00
\\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":""}}}
2021-04-07 14:10:18 +01:00
, null ) ;
try server . request ( " textDocument/completion " ,
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":0,"character":0}}
, null ) ;
2020-07-02 12:03:24 +01:00
}
2020-06-30 13:46:43 +01:00
2021-04-07 14:10:18 +01:00
test " Request completion with no trailing whitespace " {
var server = try Server . start ( initialize_msg , null ) ;
2021-12-30 05:56:17 +00:00
defer server . shutdown ( ) ;
2021-04-07 14:10:18 +01:00
try server . request ( " textDocument/didOpen " ,
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");\nc"}}
, null ) ;
2021-08-30 13:54:47 +01:00
2021-04-07 14:10:18 +01:00
try server . request ( " textDocument/completion " ,
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
,
2022-06-24 10:27:53 +01:00
\\{"isIncomplete":false,"items":[{"label":"std","kind":21,"textEdit":null,"filterText":null,"insertText":"std","insertTextFormat":1,"detail":"const std = @import(\"std\")","documentation":null,"sortText":"1_std"}]}
2021-04-07 14:10:18 +01:00
) ;
2020-06-30 13:46:43 +01:00
}
2020-07-02 12:03:24 +01:00
2021-04-15 10:07:43 +01:00
test " Encoded space in file name and usingnamespace on non-existing symbol " {
var server = try Server . start ( initialize_msg , null ) ;
2021-12-30 05:56:17 +00:00
defer server . shutdown ( ) ;
2021-04-15 10:07:43 +01:00
try server . request ( " textDocument/didOpen " ,
\\{"textDocument":{"uri":"file:///%20test.zig","languageId":"zig","version":420,"text":"usingnamespace a.b;\nb."}}
, null ) ;
try server . request ( " textDocument/completion " ,
\\{"textDocument":{"uri":"file:///%20test.zig"}, "position":{"line":1,"character":2}}
,
\\{"isIncomplete":false,"items":[]}
) ;
}
test " Self-referential definition " {
var server = try Server . start ( initialize_msg , null ) ;
2021-12-30 05:56:17 +00:00
defer server . shutdown ( ) ;
2021-08-30 13:54:47 +01:00
2021-04-15 10:07:43 +01:00
try server . request ( " textDocument/didOpen " ,
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const h = h(0);\nc"}}
, null ) ;
try server . request ( " textDocument/completion " ,
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
,
\\{"isIncomplete":false,"items":[{"label":"h","kind":21,"textEdit":null,"filterText":null,"insertText":"h","insertTextFormat":1,"detail":"const h = h(0)","documentation":null}]}
) ;
}
2021-08-30 13:54:47 +01:00
2022-05-29 18:45:08 +01:00
// This test as written depends on the configuration in the *host* machines zls.json, if `enable_snippets` is true then
// the insert text is "w()" if it is false it is "w"
//
// test "Missing return type" {
// var server = try Server.start(initialize_msg, null);
// defer server.shutdown();
// try server.request("textDocument/didOpen",
// \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"fn w() {}\nc"}}
// , null);
// try server.request("textDocument/completion",
// \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
// ,
// \\{"isIncomplete":false,"items":[{"label":"w","kind":3,"textEdit":null,"filterText":null,"insertText":"w","insertTextFormat":1,"detail":"fn","documentation":null}]}
// );
// }
2021-04-15 10:07:43 +01:00
test " Pointer and optional deref " {
var server = try Server . start ( initialize_msg , null ) ;
2021-12-30 05:56:17 +00:00
defer server . shutdown ( ) ;
2021-08-30 13:54:47 +01:00
2021-04-15 10:07:43 +01:00
try server . request ( " textDocument/didOpen " ,
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"var value: ?struct { data: i32 = 5 } = null;const ptr = &value;\nconst a = ptr.*.?."}}
, null ) ;
try server . request ( " textDocument/completion " ,
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":18}}
,
\\{"isIncomplete":false,"items":[{"label":"data","kind":5,"textEdit":null,"filterText":null,"insertText":"data","insertTextFormat":1,"detail":"data: i32 = 5","documentation":null}]}
) ;
}
2021-04-07 14:10:18 +01:00
test " Request utf-8 offset encoding " {
var server = try Server . start ( initialize_msg_offs ,
\\{"offsetEncoding":"utf-8","capabilities":{"signatureHelpProvider":{"triggerCharacters":["("],"retriggerCharacters":[","]},"textDocumentSync":1,"renameProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",":","@"]},"documentHighlightProvider":false,"hoverProvider":true,"codeActionProvider":false,"declarationProvider":true,"definitionProvider":true,"typeDefinitionProvider":true,"implementationProvider":false,"referencesProvider":true,"documentSymbolProvider":true,"colorProvider":false,"documentFormattingProvider":true,"documentRangeFormattingProvider":false,"foldingRangeProvider":false,"selectionRangeProvider":false,"workspaceSymbolProvider":false,"rangeProvider":false,"documentProvider":true,"workspace":{"workspaceFolders":{"supported":false,"changeNotifications":false}},"semanticTokensProvider":{"full":true,"range":false,"legend":{"tokenTypes":["type","parameter","variable","enumMember","field","errorTag","function","keyword","comment","string","number","operator","builtin","label","keywordLiteral"],"tokenModifiers":["namespace","struct","enum","union","opaque","declaration","async","documentation","generic"]}}},"serverInfo":{"name":"zls","version":"0.1.0"}}
) ;
2021-12-30 05:56:17 +00:00
defer server . shutdown ( ) ;
2020-07-03 10:00:00 +01:00
}
2021-04-15 10:07:43 +01:00
// not fixed yet!
// test "Self-referential import" {
// var server = try Server.start(initialize_msg, null);
// defer server.shutdown();
// try server.request("textDocument/didOpen",
// \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const a = @import(\"test.zig\").a;\nc"}}
// , null);
// try server.request("textDocument/completion",
// \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
// ,
// \\{"isIncomplete":false,"items":[]}
// );
// }