diff options
| author | DanielGavin <danielgavin5@hotmail.com> | 2020-11-07 15:44:30 +0100 |
|---|---|---|
| committer | DanielGavin <danielgavin5@hotmail.com> | 2020-11-07 15:44:30 +0100 |
| commit | 6ec424ed8d34cf8a5f51e277a20429741b33ee96 (patch) | |
| tree | 6d0eed732e01d19effbae5cc525a9c2f6e28534f /src/server/documents.odin | |
| parent | 3f1027bd4003cdb9fdc80665548181df2fb60810 (diff) | |
complete refractor of the project into packages
Diffstat (limited to 'src/server/documents.odin')
| -rw-r--r-- | src/server/documents.odin | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/src/server/documents.odin b/src/server/documents.odin new file mode 100644 index 0000000..59eeadd --- /dev/null +++ b/src/server/documents.odin @@ -0,0 +1,402 @@ +package server + +import "core:strings" +import "core:fmt" +import "core:log" +import "core:os" +import "core:odin/parser" +import "core:odin/ast" +import "core:odin/tokenizer" +import "core:path" + +import "shared:common" + +ParserError :: struct { + message: string, + line: int, + column: int, + file: string, + offset: int, +}; + + +Package :: struct { + documents: [dynamic]^Document, +}; + +Document :: struct { + uri: common.Uri, + text: [] u8, + used_text: int, //allow for the text to be reallocated with more data than needed + client_owned: bool, + diagnosed_errors: bool, + ast: ast.File, + package_name: string, + imports: [] string, +}; + +DocumentStorage :: struct { + documents: map [string] Document, + packages: map [string] Package, +}; + +document_storage: DocumentStorage; + + +document_get :: proc(uri_string: string) -> ^Document { + + uri, parsed_ok := common.parse_uri(uri_string, context.temp_allocator); + + if !parsed_ok { + return nil; + } + + return &document_storage.documents[uri.path]; +} + +/* + Note(Daniel, Should there be reference counting of documents or just clear everything on workspace change? + You usually always need the documents that are loaded in core files, your own files, etc.) + */ + +/* + Server opens a new document with text from filesystem +*/ +document_new :: proc(path: string, config: ^common.Config) -> common.Error { + + text, ok := os.read_entire_file(path); + + uri := common.create_uri(path); + + if !ok { + log.error("Failed to parse uri"); + return .ParseError; + } + + document := Document { + uri = uri, + text = transmute([] u8)text, + client_owned = false, + used_text = len(text), + }; + + + document_storage.documents[path] = document; + + return .None; +} + +/* + Client opens a document with transferred text +*/ + +document_open :: proc(uri_string: string, text: string, config: ^common.Config, writer: ^Writer) -> common.Error { + + uri, parsed_ok := common.parse_uri(uri_string); + + log.infof("document_open: %v", uri_string); + + if !parsed_ok { + log.error("Failed to parse uri"); + return .ParseError; + } + + if document := &document_storage.documents[uri.path]; document != nil { + + if document.client_owned { + log.errorf("Client called open on an already open document: %v ", document.uri.path); + return .InvalidRequest; + } + + if document.text != nil { + delete(document.text); + } + + if len(document.uri.uri) > 0 { + common.delete_uri(document.uri); + } + + document.uri = uri; + document.client_owned = true; + document.text = transmute([] u8)text; + document.used_text = len(document.text); + + if err := document_refresh(document, config, writer, true); err != .None { + return err; + } + + } + + else { + + document := Document { + uri = uri, + text = transmute([] u8)text, + client_owned = true, + used_text = len(text), + }; + + if err := document_refresh(&document, config, writer, true); err != .None { + return err; + } + + document_storage.documents[uri.path] = document; + } + + + + //hmm feels like odin needs some ownership semantic + delete(uri_string); + + return .None; +} + +/* + Function that applies changes to the given document through incremental syncronization + */ +document_apply_changes :: proc(uri_string: string, changes: [dynamic] TextDocumentContentChangeEvent, config: ^common.Config, writer: ^Writer) -> common.Error { + + uri, parsed_ok := common.parse_uri(uri_string, context.temp_allocator); + + if !parsed_ok { + return .ParseError; + } + + document := &document_storage.documents[uri.path]; + + if !document.client_owned { + log.errorf("Client called change on an document not opened: %v ", document.uri.path); + return .InvalidRequest; + } + + for change in changes { + + absolute_range, ok := common.get_absolute_range(change.range, document.text[:document.used_text]); + + if !ok { + return .ParseError; + } + + //lower bound is before the change + lower := document.text[:absolute_range.start]; + + //new change between lower and upper + middle := change.text; + + //upper bound is after the change + upper := document.text[absolute_range.end:document.used_text]; + + //total new size needed + document.used_text = len(lower) + len(change.text) + len(upper); + + //Reduce the amount of allocation by allocating more memory than needed + if document.used_text > len(document.text) { + new_text := make([]u8, document.used_text * 2); + + //join the 3 splices into the text + copy(new_text, lower); + copy(new_text[len(lower):], middle); + copy(new_text[len(lower)+len(middle):], upper); + + delete(document.text); + + document.text = new_text; + } + + else { + //order matters here, we need to make sure we swap the data already in the text before the middle + copy(document.text, lower); + copy(document.text[len(lower)+len(middle):], upper); + copy(document.text[len(lower):], middle); + } + + } + + return document_refresh(document, config, writer, true); +} + +document_close :: proc(uri_string: string) -> common.Error { + + uri, parsed_ok := common.parse_uri(uri_string, context.temp_allocator); + + if !parsed_ok { + return .ParseError; + } + + document := &document_storage.documents[uri.path]; + + if document == nil || !document.client_owned { + log.errorf("Client called close on a document that was never opened: %v ", document.uri.path); + return .InvalidRequest; + } + + document.client_owned = false; + + return .None; +} + + + +document_refresh :: proc(document: ^Document, config: ^common.Config, writer: ^Writer, parse_imports: bool) -> common.Error { + + errors, ok := parse_document(document, config); + + if !ok { + return .ParseError; + } + + //right now we don't allow to writer errors out from files read from the file directory, core files, etc. + if writer != nil && len(errors) > 0 { + document.diagnosed_errors = true; + + params := NotificationPublishDiagnosticsParams { + uri = document.uri.uri, + diagnostics = make([] Diagnostic, len(errors), context.temp_allocator), + }; + + for error, i in errors { + + params.diagnostics[i] = Diagnostic { + range = common.Range { + start = common.Position { + line = error.line - 1, + character = 0, + }, + end = common.Position { + line = error.line, + character = 0, + }, + }, + severity = DiagnosticSeverity.Error, + code = "test", + message = error.message, + }; + + } + + notifaction := Notification { + jsonrpc = "2.0", + method = "textDocument/publishDiagnostics", + params = params, + }; + + send_notification(notifaction, writer); + + } + + if writer != nil && len(errors) == 0 { + + //send empty diagnosis to remove the clients errors + if document.diagnosed_errors { + + notifaction := Notification { + jsonrpc = "2.0", + method = "textDocument/publishDiagnostics", + + params = NotificationPublishDiagnosticsParams { + uri = document.uri.uri, + diagnostics = make([] Diagnostic, len(errors), context.temp_allocator), + }, + }; + + document.diagnosed_errors = false; + + send_notification(notifaction, writer); + } + + } + + return .None; +} + +document_load_package :: proc(package_directory: string, config: ^common.Config) -> common.Error { + + fd, err := os.open(package_directory); + + if err != 0 { + return .ParseError; + } + + files: []os.File_Info; + files, err = os.read_dir(fd, 100, context.temp_allocator); + + for file in files { + + //if we have never encountered the document + if _, ok := document_storage.documents[file.fullpath]; !ok { + + if doc_err := document_new(file.fullpath, config); doc_err != .None { + return doc_err; + } + + } + + } + + return .None; +} + + +current_errors: [dynamic] ParserError; + +parser_error_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { + error := ParserError { line = pos.line, column = pos.column, file = pos.file, + offset = pos.offset, message = fmt.tprintf(msg, ..args) }; + append(¤t_errors, error); +} + +parser_warning_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { + +} + +parse_document :: proc(document: ^Document, config: ^common.Config) -> ([] ParserError, bool) { + + p := parser.Parser { + err = parser_error_handler, + warn = parser_warning_handler, + }; + + current_errors = make([dynamic] ParserError, context.temp_allocator); + + document.ast = ast.File { + fullpath = document.uri.path, + src = document.text[:document.used_text], + }; + + parser.parse_file(&p, &document.ast); + + /* + if document.imports != nil { + delete(document.imports); + delete(document.package_name); + } + document.imports = make([]string, len(document.ast.imports)); + document.package_name = document.ast.pkg_name; + + for imp, index in document.ast.imports { + + //collection specified + if i := strings.index(imp.fullpath, ":"); i != -1 { + + collection := imp.fullpath[1:i]; + p := imp.fullpath[i+1:len(imp.fullpath)-1]; + + dir, ok := config.collections[collection]; + + if !ok { + continue; + } + + document.imports[index] = path.join(dir, p); + + } + + //relative + else { + + } + } + */ + + return current_errors[:], true; +}
\ No newline at end of file |