aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDanielGavin <danielgavin5@hotmail.com>2025-04-05 20:02:58 +0200
committerDanielGavin <danielgavin5@hotmail.com>2025-04-05 20:02:58 +0200
commita500226c10e991506c42a4858ffd7d1d33f552e6 (patch)
tree56f2df9234eeb0f00eb49848edc6c676cfba34ee /src
parent1e44e3d78ad8a74ef09c7f54a6f6d3f7df517f8e (diff)
Add support for filewatching from the client
Diffstat (limited to 'src')
-rw-r--r--src/server/build.odin102
-rw-r--r--src/server/requests.odin159
-rw-r--r--src/server/response.odin20
-rw-r--r--src/server/types.odin45
4 files changed, 243 insertions, 83 deletions
diff --git a/src/server/build.odin b/src/server/build.odin
index a681201..d3b8572 100644
--- a/src/server/build.odin
+++ b/src/server/build.odin
@@ -166,6 +166,108 @@ try_build_package :: proc(pkg_name: string) {
}
}
+
+remove_index_file :: proc(uri: common.Uri) -> common.Error {
+ ok: bool
+
+ fullpath := uri.path
+
+ when ODIN_OS == .Windows {
+ fullpath, _ = filepath.to_slash(fullpath, context.temp_allocator)
+ }
+
+ corrected_uri := common.create_uri(fullpath, context.temp_allocator)
+
+ for k, &v in indexer.index.collection.packages {
+ for k2, v2 in v.symbols {
+ if strings.equal_fold(corrected_uri.uri, v2.uri) {
+ free_symbol(v2, indexer.index.collection.allocator)
+ delete_key(&v.symbols, k2)
+ }
+ }
+
+ for method, &symbols in v.methods {
+ for i := len(symbols) - 1; i >= 0; i -= 1 {
+ #no_bounds_check symbol := symbols[i]
+ if strings.equal_fold(corrected_uri.uri, symbol.uri) {
+ unordered_remove(&symbols, i)
+ }
+ }
+ }
+ }
+
+ return .None
+}
+
+index_file :: proc(uri: common.Uri, text: string) -> common.Error {
+ ok: bool
+
+ fullpath := uri.path
+
+ p := parser.Parser {
+ err = log_error_handler,
+ warn = log_warning_handler,
+ flags = {.Optional_Semicolons},
+ }
+
+ when ODIN_OS == .Windows {
+ correct := common.get_case_sensitive_path(fullpath, context.temp_allocator)
+ fullpath, _ = filepath.to_slash(correct, context.temp_allocator)
+ }
+
+ dir := filepath.base(filepath.dir(fullpath, context.temp_allocator))
+
+ pkg := new(ast.Package)
+ pkg.kind = .Normal
+ pkg.fullpath = fullpath
+ pkg.name = dir
+
+ if dir == "runtime" {
+ pkg.kind = .Runtime
+ }
+
+ file := ast.File {
+ fullpath = fullpath,
+ src = text,
+ pkg = pkg,
+ }
+
+ ok = parser.parse_file(&p, &file)
+
+ if !ok {
+ if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") {
+ log.errorf("error in parse file for indexing %v", fullpath)
+ }
+ }
+
+ corrected_uri := common.create_uri(fullpath, context.temp_allocator)
+
+ for k, &v in indexer.index.collection.packages {
+ for k2, v2 in v.symbols {
+ if corrected_uri.uri == v2.uri {
+ free_symbol(v2, indexer.index.collection.allocator)
+ delete_key(&v.symbols, k2)
+ }
+ }
+
+ for method, &symbols in v.methods {
+ for i := len(symbols) - 1; i >= 0; i -= 1 {
+ #no_bounds_check symbol := symbols[i]
+ if corrected_uri.uri == symbol.uri {
+ unordered_remove(&symbols, i)
+ }
+ }
+ }
+ }
+
+ if ret := collect_symbols(&indexer.index.collection, file, corrected_uri.uri); ret != .None {
+ log.errorf("failed to collect symbols on save %v", ret)
+ }
+
+ return .None
+}
+
+
setup_index :: proc() {
build_cache.loaded_pkgs = make(map[string]PackageCacheInfo, 50, context.allocator)
symbol_collection := make_symbol_collection(context.allocator, &common.config)
diff --git a/src/server/requests.odin b/src/server/requests.odin
index f884b0d..f4e2d4b 100644
--- a/src/server/requests.odin
+++ b/src/server/requests.odin
@@ -27,27 +27,6 @@ Header :: struct {
content_type: string,
}
-RequestType :: enum {
- Initialize,
- Initialized,
- Shutdown,
- Exit,
- DidOpen,
- DidChange,
- DidClose,
- DidSave,
- Definition,
- Completion,
- SignatureHelp,
- DocumentSymbol,
- SemanticTokensFull,
- SemanticTokensRange,
- FormatDocument,
- Hover,
- CancelRequest,
- InlayHint,
-}
-
RequestInfo :: struct {
root: json.Value,
params: json.Value,
@@ -58,7 +37,6 @@ RequestInfo :: struct {
result: common.Error,
}
-
make_response_message :: proc(id: RequestId, params: ResponseParams) -> ResponseMessage {
return ResponseMessage{jsonrpc = "2.0", id = id, result = params}
}
@@ -122,6 +100,11 @@ thread_request_main :: proc(data: rawptr) {
#partial switch v in id_value {
case json.String:
id = v
+ //Hack to support dynamic registering without changing too much
+ if v == "REGISTER_DYNAMIC_CAPABILITIES" {
+ json.destroy_value(root)
+ continue
+ }
case json.Integer:
id = v
case:
@@ -260,15 +243,17 @@ call_map: map[string]proc(_: json.Value, _: RequestId, _: ^common.Config, _: ^Wr
"window/progress" = request_noop,
"workspace/symbol" = request_workspace_symbols,
"workspace/didChangeConfiguration" = notification_workspace_did_change_configuration,
+ "workspace/didChangeWatchedFiles" = notification_did_change_watched_files,
}
notification_map: map[string]bool = {
- "textDocument/didOpen" = true,
- "textDocument/didChange" = true,
- "textDocument/didClose" = true,
- "textDocument/didSave" = true,
- "initialized" = true,
- "window/progress" = true,
+ "textDocument/didOpen" = true,
+ "textDocument/didChange" = true,
+ "textDocument/didClose" = true,
+ "textDocument/didSave" = true,
+ "initialized" = true,
+ "window/progress" = true,
+ "workspace/didChangeWatchedFiles" = true,
}
consume_requests :: proc(config: ^common.Config, writer: ^Writer) -> bool {
@@ -721,9 +706,34 @@ request_initialize :: proc(
try_build_package(pkg)
}
+ register_dynamic_capabilities(writer)
+
return .None
}
+register_dynamic_capabilities :: proc(writer: ^Writer) {
+ params: RegistrationParams
+
+ registration: Registration
+
+ registration.id = "GLOBAL_ODIN_FILES"
+ registration.method = "workspace/didChangeWatchedFiles"
+ registration.registerOptions = DidChangeWatchedFilesRegistrationOptions {
+ watchers = []FileSystemWatcher{{globPattern = "**/*.odin"}},
+ }
+
+ params.registrations = {registration}
+
+ request_message := RequestMessage {
+ jsonrpc = "2.0",
+ method = "client/registerCapability",
+ params = params,
+ id = "REGISTER_DYNAMIC_CAPABILITIES",
+ }
+
+ send_request(request_message, writer)
+}
+
request_initialized :: proc(
params: json.Value,
id: RequestId,
@@ -1012,69 +1022,19 @@ notification_did_save :: proc(
return .ParseError
}
- fullpath := uri.path
-
- p := parser.Parser {
- err = log_error_handler,
- warn = log_warning_handler,
- flags = {.Optional_Semicolons},
+ if result := index_file(uri, save_params.text); result != .None {
+ return result
}
+ fullpath := uri.path
+
when ODIN_OS == .Windows {
correct := common.get_case_sensitive_path(fullpath, context.temp_allocator)
fullpath, _ = filepath.to_slash(correct, context.temp_allocator)
}
- dir := filepath.base(filepath.dir(fullpath, context.temp_allocator))
-
- pkg := new(ast.Package)
- pkg.kind = .Normal
- pkg.fullpath = fullpath
- pkg.name = dir
-
- if dir == "runtime" {
- pkg.kind = .Runtime
- }
-
- file := ast.File {
- fullpath = fullpath,
- src = save_params.text,
- pkg = pkg,
- }
-
- ok = parser.parse_file(&p, &file)
-
- if !ok {
- if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") {
- log.errorf("error in parse file for indexing %v", fullpath)
- }
- }
-
corrected_uri := common.create_uri(fullpath, context.temp_allocator)
- for k, &v in indexer.index.collection.packages {
- for k2, v2 in v.symbols {
- if corrected_uri.uri == v2.uri {
- free_symbol(v2, indexer.index.collection.allocator)
- delete_key(&v.symbols, k2)
- }
- }
-
- for method, &symbols in v.methods {
- for i := 0; i < len(symbols); i += 1 {
- #no_bounds_check symbol := symbols[i]
- if corrected_uri.uri == symbol.uri {
- unordered_remove(&symbols, i)
- i -= 1
- }
- }
- }
- }
-
- if ret := collect_symbols(&indexer.index.collection, file, corrected_uri.uri); ret != .None {
- log.errorf("failed to collect symbols on save %v", ret)
- }
-
check(config.profile.checker_path[:], corrected_uri, writer, config)
return .None
@@ -1427,6 +1387,41 @@ request_references :: proc(
return .None
}
+notification_did_change_watched_files :: proc(
+ params: json.Value,
+ id: RequestId,
+ config: ^common.Config,
+ writer: ^Writer,
+) -> common.Error {
+ params_object, ok := params.(json.Object)
+
+ if !ok {
+ return .ParseError
+ }
+
+ did_change_watched_files_params: DidChangeWatchedFilesParams
+
+ if unmarshal(params, did_change_watched_files_params, context.temp_allocator) != nil {
+ return .ParseError
+ }
+
+ for change in did_change_watched_files_params.changes {
+ if change.type == cast(int)FileChangeType.Deleted {
+ if uri, ok := common.parse_uri(change.uri, context.temp_allocator); ok {
+ remove_index_file(uri)
+ }
+ } else {
+ if uri, ok := common.parse_uri(change.uri, context.temp_allocator); ok {
+ if data, ok := os.read_entire_file(uri.path); ok {
+ index_file(uri, cast(string)data)
+ }
+ }
+ }
+ }
+
+ return .None
+}
+
notification_workspace_did_change_configuration :: proc(
params: json.Value,
id: RequestId,
diff --git a/src/server/response.odin b/src/server/response.odin
index 1019eb6..7765b34 100644
--- a/src/server/response.odin
+++ b/src/server/response.odin
@@ -23,6 +23,26 @@ send_notification :: proc(notification: Notification, writer: ^Writer) -> bool {
return true
}
+send_request :: proc(request: RequestMessage, writer: ^Writer) -> bool {
+ data, error := marshal(request, {}, context.temp_allocator)
+
+ header := fmt.tprintf("Content-Length: %v\r\n\r\n", len(data))
+
+ if error != nil {
+ return false
+ }
+
+ if !write_sized(writer, transmute([]u8)header) {
+ return false
+ }
+
+ if !write_sized(writer, data) {
+ return false
+ }
+
+ return true
+}
+
send_response :: proc(response: ResponseMessage, writer: ^Writer) -> bool {
data, error := marshal(response, {}, context.temp_allocator)
diff --git a/src/server/types.odin b/src/server/types.odin
index 2dd75b4..c9107ff 100644
--- a/src/server/types.odin
+++ b/src/server/types.odin
@@ -33,6 +33,15 @@ ResponseParams :: union {
common.Range,
}
+RequestMessage :: struct {
+ jsonrpc: string,
+ method: string,
+ id: RequestId,
+ params: union {
+ RegistrationParams,
+ },
+}
+
ResponseMessage :: struct {
jsonrpc: string,
id: RequestId,
@@ -84,6 +93,33 @@ RequestInitializeParams :: struct {
clientInfo: ClientInfo,
}
+FileChangeType :: enum {
+ Created = 1,
+ Changed = 2,
+ Deleted = 3,
+}
+
+FileEvent :: struct {
+ uri: string,
+ type: int,
+}
+
+DidChangeWatchedFilesParams :: struct {
+ changes: [dynamic]FileEvent,
+}
+
+Registration :: struct {
+ id: string,
+ method: string,
+ registerOptions: union {
+ DidChangeWatchedFilesRegistrationOptions,
+ },
+}
+
+RegistrationParams :: struct {
+ registrations: []Registration,
+}
+
ClientInfo :: struct {
name: string,
}
@@ -109,11 +145,14 @@ ServerCapabilities :: struct {
documentLinkProvider: DocumentLinkOptions,
}
+DidChangeWatchedFilesRegistrationOptions :: struct {
+ watchers: []FileSystemWatcher,
+}
+
RenameOptions :: struct {
prepareProvider: bool,
}
-
CompletionOptions :: struct {
resolveProvider: bool,
triggerCharacters: []string,
@@ -341,6 +380,10 @@ TextDocumentSyncOptions :: struct {
save: SaveOptions,
}
+FileSystemWatcher :: struct {
+ globPattern: string,
+}
+
OlsConfig :: struct {
collections: [dynamic]OlsConfigCollection,
thread_pool_count: Maybe(int),