From 49a83b7a21127197c862cd57ed28067dec296a03 Mon Sep 17 00:00:00 2001 From: Alan Date: Tue, 23 Jan 2024 17:38:20 +0800 Subject: [PATCH] Fix #7982 - roo javascript completion --- src/Application.vala | 5 +- src/Builder4/DialogConfirm.bjs | 2 +- src/Builder4/DialogConfirm.vala | 106 ++--- src/JsRender/Node.vala | 3 + src/JsRender/NodeToJs.vala | 1 + src/JsRender/NodeToVala.vala | 2 + src/Palete/CompletionProvider.vala | 13 +- src/Palete/Javascript.vala | 2 +- src/Palete/LanguageClient.vala | 546 +--------------------- src/Palete/LanguageClientDummy.vala | 23 +- src/Palete/LanguageClientJavascript.vala | 108 +++-- src/Palete/LanguageClientVala.vala | 548 ++++++++++++++++++++++- src/Project/Roo.vala | 32 +- 13 files changed, 736 insertions(+), 655 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 9eb45bd67..7584337cf 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -523,10 +523,7 @@ } var loop = new MainLoop(); GLib.Timeout.add_seconds(1, () => { - if (!ls.isReady()) { - GLib.debug("waiting for server to be ready"); - return true; - } + GLib.debug("Sending document_open"); // it's ready.. diff --git a/src/Builder4/DialogConfirm.bjs b/src/Builder4/DialogConfirm.bjs index 045bf892d..551f4538e 100644 --- a/src/Builder4/DialogConfirm.bjs +++ b/src/Builder4/DialogConfirm.bjs @@ -15,7 +15,7 @@ " this.el.response(Gtk.ResponseType.CANCEL);", " this.el.hide();", " return true;", - " ", + "", "}", "" ] diff --git a/src/Builder4/DialogConfirm.vala b/src/Builder4/DialogConfirm.vala index dfb48adfd..ef1281c1b 100644 --- a/src/Builder4/DialogConfirm.vala +++ b/src/Builder4/DialogConfirm.vala @@ -1,53 +1,53 @@ - static DialogConfirm _DialogConfirm; - - public class DialogConfirm : Object - { - public Gtk.MessageDialog el; - private DialogConfirm _this; - - public static DialogConfirm singleton() - { - if (_DialogConfirm == null) { - _DialogConfirm= new DialogConfirm(); - } - return _DialogConfirm; - } - - // my vars (def) - - // ctor - public DialogConfirm() - { - _this = this; - this.el = new Gtk.MessageDialog( null, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Test" ); - - // my vars (dec) - - // set gobject values - this.el.title = "Please Confirm "; - this.el.name = "DialogConfirm"; - this.el.modal = true; - this.el.use_markup = true; - - //listeners - this.el.close_request.connect( (event) => { - this.el.response(Gtk.ResponseType.CANCEL); - this.el.hide(); - return true; - - }); - } - - // user defined functions - public void showIt // caller needs to connect to the response - to get the result. - - (string title, string msg) { - //if (!this.el) { this.init(); } - //this.success = success; - this.el.title = title; - this.el.text = msg; - this.el.show(); - - - } - } +static DialogConfirm _DialogConfirm; + +public class DialogConfirm : Object +{ + public Gtk.MessageDialog el; + private DialogConfirm _this; + + public static DialogConfirm singleton() + { + if (_DialogConfirm == null) { + _DialogConfirm= new DialogConfirm(); + } + return _DialogConfirm; + } + + // my vars (def) + + // ctor + public DialogConfirm() + { + _this = this; + this.el = new Gtk.MessageDialog( null, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Test" ); + + // my vars (dec) + + // set gobject values + this.el.title = "Please Confirm "; + this.el.name = "DialogConfirm"; + this.el.modal = true; + this.el.use_markup = true; + + //listeners + this.el.close_request.connect( (event) => { + this.el.response(Gtk.ResponseType.CANCEL); + this.el.hide(); + return true; + + }); + } + + // user defined functions + public void showIt // caller needs to connect to the response - to get the result. + + (string title, string msg) { + //if (!this.el) { this.init(); } + //this.success = success; + this.el.title = title; + this.el.text = msg; + this.el.show(); + + + } +} diff --git a/src/JsRender/Node.vala b/src/JsRender/Node.vala index 497c435d4..7c448bb06 100644 --- a/src/JsRender/Node.vala +++ b/src/JsRender/Node.vala @@ -111,6 +111,9 @@ public class JsRender.Node : GLib.Object { public Gee.ArrayList node_lines; public Gee.HashMap node_lines_map; // store of l:xxx or p:.... + + public string node_pad = ""; + private int _updated_count = 0; public int updated_count { get { diff --git a/src/JsRender/NodeToJs.vala b/src/JsRender/NodeToJs.vala index 008a1f478..eae9235a0 100644 --- a/src/JsRender/NodeToJs.vala +++ b/src/JsRender/NodeToJs.vala @@ -46,6 +46,7 @@ public class JsRender.NodeToJs : Object { this.node = node; this.doubleStringProps = doubleStringProps; this.pad = pad; + this.node.node_pad = pad; //this.els = new Gee.ArrayList(); //this.ar_props = new Gee.HashMap(); diff --git a/src/JsRender/NodeToVala.vala b/src/JsRender/NodeToVala.vala index ce1991e1f..76480eed6 100644 --- a/src/JsRender/NodeToVala.vala +++ b/src/JsRender/NodeToVala.vala @@ -60,6 +60,8 @@ public class JsRender.NodeToVala : Object { this.inpad = string.nfill(depth > 0 ? 1 : 0, '\t'); } this.pad = this.inpad + "\t"; + + this.node.node_pad = this.inpad; this.ipad = this.inpad + "\t\t"; this.cls = node.xvala_cls; this.xcls = node.xvala_xcls; diff --git a/src/Palete/CompletionProvider.vala b/src/Palete/CompletionProvider.vala index 9c365af04..06259d4af 100644 --- a/src/Palete/CompletionProvider.vala +++ b/src/Palete/CompletionProvider.vala @@ -197,14 +197,21 @@ namespace Palete { var offset = end.get_line_offset(); if (this.editor.prop != null) { // tried line -1 (does not work) - + GLib.debug("node pad = '%s' %d", this.editor.node.node_pad, this.editor.node.node_pad.length); + line += this.editor.prop.start_line ; // this is based on Gtk using tabs (hence 1/2 chars); - offset += this.editor.file.file_namespace == "" ? 1 : 2; + offset += this.editor.node.node_pad.length; + // javascript listeners are indented 2 more spaces. + if (this.editor.prop.ptype == JsRender.NodePropType.LISTENER) { + offset += 2; + } } - this.file.getLanguageServer().document_change_real(this.file, this.editor.tempFileContents()); + this.file.getLanguageServer().document_change_force(this.file, this.editor.tempFileContents()); try { + GLib.debug("sending request to language server %s", this.file.getLanguageServer().get_type().name()); + res = yield this.file.getLanguageServer().completion(this.file, line, offset, 1); } catch (GLib.Error e) { GLib.debug("got error %s", e.message); diff --git a/src/Palete/Javascript.vala b/src/Palete/Javascript.vala index 4edd52124..c70eed98f 100644 --- a/src/Palete/Javascript.vala +++ b/src/Palete/Javascript.vala @@ -97,7 +97,7 @@ namespace Palete { //GLib.debug("go t error %d %s", (int)ex.get_line_number() , ex.get_message() ); - var ret = new CompileError.new_jserror(file, "ERR", (int) ex.get_line_number(), ex.get_message()); + var ret = new CompileError.new_jserror(file, "ERR", (int) ex.get_line_number() -1 , ex.get_message()); ar.append(ret); diff --git a/src/Palete/LanguageClient.vala b/src/Palete/LanguageClient.vala index 245e3217c..a2dee4c32 100644 --- a/src/Palete/LanguageClient.vala +++ b/src/Palete/LanguageClient.vala @@ -35,37 +35,10 @@ namespace Palete { public abstract class LanguageClient : Jsonrpc.Server { public Project.Project project; - private GLib.SubprocessLauncher launcher = null; - private GLib.Subprocess? subprocess = null; - private IOStream? subprocess_stream = null; - public Jsonrpc.Client? jsonrpc_client = null; - Gee.ArrayList open_files; - private JsRender.JsRender? _change_queue_file = null; - private string change_queue_file_source = ""; - - JsRender.JsRender? change_queue_file { - set { - this.change_queue_file_source = value == null ? "" : value.toSource(); - this._change_queue_file = value; - } - get { - return this._change_queue_file; - } - } + - uint change_queue_id = 0; - int countdown = 0; - protected bool initialized = false; - bool sent_shutdown = false; - private bool _closed = false; - private bool closed { - get { return this._closed ; } - set { - GLib.debug("closed has been set? to %s" , value ? "TRUE" : "FALSE" ); - this._closed = value; - } - } + public signal void log(LanguageClientAction action, string message); @@ -74,108 +47,13 @@ namespace Palete { { // extend versions will proably call initialize to start and connect to server. this.project = project; - this.open_files = new Gee.ArrayList(); - this.change_queue_id = GLib.Timeout.add_seconds(1, () => { - if (this.change_queue_file == null) { - return true; - } - this.countdown--; - if (this.countdown < 0){ - this.document_change_real(this.change_queue_file, this.change_queue_file_source); - this.change_queue_file = null; - - } - return true; - }); + + } - public bool initProcess(string process_path) - { - this.onClose(); - this.log(LanguageClientAction.LAUNCH, process_path); - GLib.debug("Launching %s", process_path); - this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE); - this.launcher.set_environ(GLib.Environ.get()); - try { - - - this.subprocess = launcher.spawnv ({ process_path }); - - this.subprocess.wait_async.begin( null, ( obj,res ) => { - try { - this.subprocess.wait_async.end(res); - } catch (GLib.Error e) { - this.log(LanguageClientAction.ERROR_START, e.message); - GLib.debug("subprocess startup error %s", e.message); - } - this.log(LanguageClientAction.EXIT, "process ended"); - GLib.debug("Subprocess ended %s", process_path); - this.onClose(); - - }); - var input_stream = this.subprocess.get_stdout_pipe (); - var output_stream = this.subprocess.get_stdin_pipe (); - - if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) { - // set nonblocking - if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true) - || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true)) - { - GLib.debug("could not set pipes to nonblocking"); - this.onClose(); - return false; - } - } - this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream); - this.accept_io_stream ( this.subprocess_stream); - } catch (GLib.Error e) { - this.log(LanguageClientAction.ERROR_START, e.message); - GLib.debug("subprocess startup error %s", e.message); - this.onClose(); - return false; - } - return true; - } - bool in_close = false; - protected void onClose() - { - if (this.in_close) { - return; - } - if (this.launcher == null) { - return; - } - this.in_close = true; - GLib.debug("onClose called"); - - if (this.jsonrpc_client != null) { - try { - this.jsonrpc_client.close(); - } catch (GLib.Error e) { - GLib.debug("rpc Error close error %s", e.message); - } - } - if (this.subprocess_stream != null) { - try { - this.subprocess_stream.close(); - } catch (GLib.Error e) { - GLib.debug("stream Error close %s", e.message); - } - } - if (this.subprocess != null) { - this.subprocess.force_exit(); - } - if (this.launcher != null) { - this.launcher.close(); - } - - this.launcher = null; - this.subprocess = null; - this.jsonrpc_client = null; - this.closed = true; - this.in_close = false; - } + + /** utility method to build variant based queries */ @@ -193,411 +71,25 @@ namespace Palete { return builder.end (); } - public override void client_accepted (Jsonrpc.Client client) - { - if (this.jsonrpc_client == null) { - this.jsonrpc_client = client; - - GLib.debug("client accepted connection - calling init server"); - this.log(LanguageClientAction.ACCEPT, "client accepted"); - - this.jsonrpc_client.notification.connect((method, paramz) => { - this.onNotification(method, paramz); - }); - - this.jsonrpc_client.failed.connect(() => { - this.log(LanguageClientAction.ERROR_RPC, "client failed"); - this.onClose(); - - GLib.debug("language server server has failed"); - }); - - this.initialize_server (); - } - - - } - public bool isReady() - { - if (this.closed) { - this.log(LanguageClientAction.RESTART,"closed is set - restarting"); - GLib.debug("server stopped = restarting"); - this.initialized = false; - this.closed = false; - this.startServer(); - foreach(var f in this.open_files) { - this.document_open(f); - } - return false; // can't do an operation yet? - - } - - if (!this.initialized) { - GLib.debug("Server has not been initialized"); - return false; - } - if (this.sent_shutdown) { - GLib.debug("Server has been started its shutting down process"); - return false; - } - // restart server.. - - - - return true; - } - - - public abstract void initialize_server(); - public abstract void startServer(); - //public abstract void initialize_server() ; - + - public void onNotification(string method, Variant? return_value) - { - switch (method) { - case "textDocument/publishDiagnostics": - this.onDiagnostic(return_value); - return; - default: - break; - - } - GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true)); - - } - - /*** - - */ - public void onDiagnostic(Variant? return_value) - { - - var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics; - this.log(LanguageClientAction.DIAG, dg.filename); - var f = this.project.getByPath(dg.filename); - if (f == null) { - //GLib.debug("no file %s", dg.uri); - this.project.updateErrorsforFile(null); - return; - } - foreach(var v in f.errorsByType.values) { - v.remove_all(); - } - foreach(var diag in dg.diagnostics) { - var ce = new CompileError.new_from_diagnostic(f, diag); - if (!f.errorsByType.has_key(ce.category)) { - f.errorsByType.set(ce.category, new GLib.ListStore(typeof(CompileError))); - } - f.errorsByType.get(ce.category).append(ce); - } - f.project.updateErrorsforFile(f); - - } - - public void document_open (JsRender.JsRender file) - { - if (!this.isReady()) { - return; - } - if (!this.open_files.contains(file)) { - this.open_files.add(file); - } - - GLib.debug ("LS sent open"); - try { - this.jsonrpc_client.send_notification ( - "textDocument/didOpen", - this.buildDict ( - textDocument : this.buildDict ( - uri: new Variant.string (file.to_url()), - languageId : new Variant.string (file.language_id()), - version : new GLib.Variant.uint64 ( (uint64) file.version), - text : new Variant.string (file.toSource()) - ) - ), - null - ); - this.log(LanguageClientAction.OPEN, file.path); - } catch( GLib.Error e) { - this.log(LanguageClientAction.ERROR_RPC, e.message); - this.onClose(); - GLib.debug ("LS sent open err %s", e.message); - } - - } - - public void document_save (JsRender.JsRender file) - { - if (!this.isReady()) { - return; - } - this.change_queue_file = null; - GLib.debug ("LS send save"); - try { - this.jsonrpc_client.send_notification ( - "textDocument/didChange", - this.buildDict ( - textDocument : this.buildDict ( ///TextDocumentItem; - uri: new GLib.Variant.string (file.to_url()), - version : new GLib.Variant.uint64 ( (uint64) file.version) - ) - ), - null - ); - this.log(LanguageClientAction.SAVE, file.path); - } catch( GLib.Error e) { - this.log(LanguageClientAction.ERROR_RPC, e.message); - GLib.debug ("LS save err %s", e.message); - this.onClose(); - } - - - } - public void document_close (JsRender.JsRender file) - { - if (!this.isReady()) { - return; - } - this.change_queue_file = null; - - if (this.open_files.contains(file)) { - this.open_files.remove(file); - } - this.log(LanguageClientAction.CLOSE, file.path); - GLib.debug ("LS send close"); - try { - this.jsonrpc_client.send_notification ( - "textDocument/didChange", - this.buildDict ( - textDocument : this.buildDict ( ///TextDocumentItem; - uri: new GLib.Variant.string (file.to_url()) - - ) - ), - null - ); - } catch( GLib.Error e) { - this.log(LanguageClientAction.ERROR_RPC, e.message); - GLib.debug ("LS close err %s", e.message); - this.onClose(); - } - - - } - - - public void document_change (JsRender.JsRender file ) - { - if (this.change_queue_file != null && this.change_queue_file.path != file.path) { - this.document_change_real(this.change_queue_file, this.change_queue_file_source); - } - - this.countdown = 3; - this.change_queue_file = file; - - - - } - - - public void document_change_real (JsRender.JsRender file, string contents) - { - if (!this.isReady()) { - return; - } - - - GLib.debug ("LS send change"); - var ar = new Json.Array(); - var obj = new Json.Object(); - obj.set_string_member("text", contents); - ar.add_object_element(obj); - var node = new Json.Node(Json.NodeType.ARRAY); - node.set_array(ar); - this.log(LanguageClientAction.CHANGE, file.path); - try { - this.jsonrpc_client.send_notification ( - "textDocument/didChange", - this.buildDict ( - textDocument : this.buildDict ( ///TextDocumentItem; - uri: new GLib.Variant.string (file.to_url()), - version : new GLib.Variant.uint64 ( (uint64) file.version) - ), - contentChanges : Json.gvariant_deserialize (node, null) - - ), - null - ); - } catch( GLib.Error e) { - this.log(LanguageClientAction.ERROR_RPC, e.message); - GLib.debug ("LS change err %s", e.message); - this.onClose(); - } - - - } - // called by close window (on last window)... - public void exit () throws GLib.Error - { - if (!this.isReady()) { - - return; - } - this.log(LanguageClientAction.TERM, "SEND exit"); - - this.jsonrpc_client.send_notification ( - "exit", - null, - null - ); - this.onClose(); - - } - // not used currently.. - public async void shutdown () throws GLib.Error - { - if (!this.isReady()) { - return; - } - this.log(LanguageClientAction.TERM, "SEND shutodwn"); - this.sent_shutdown = true; - Variant? return_value; - yield this.jsonrpc_client.call_async ( - "shutdown", - null, - null, - out return_value - ); - GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); - } - //public async ??/symbol (string symbol) throws GLib.Error { - // and now for the important styff.. + public abstract void initialize_server(); + - /* + public abstract void document_open (JsRender.JsRender file) ; + public abstract void document_save (JsRender.JsRender file); + public abstract void document_close (JsRender.JsRender file); + public abstract void document_change (JsRender.JsRender file ); + public abstract void document_change_force (JsRender.JsRender file, string contents ); + public abstract void exit () throws GLib.Error; + public abstract async void shutdown () throws GLib.Error; + public abstract async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error; + public abstract async Gee.ArrayList syntax (JsRender.JsRender file) throws GLib.Error; - @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion? - */ - public async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error - { - /* partial_result_token , work_done_token context = null) */ - GLib.debug("get completion %s @ %d:%d", file.relpath, line, offset); - - var ret = new Lsp.CompletionList(); - - if (!this.isReady()) { - GLib.debug("completion - language server not ready"); - return ret; - } - // make sure completion has the latest info.. - //if (this.change_queue_file != null && this.change_queue_file.path != file.path) { - // this.document_change_real(this.change_queue_file, this.change_queue_file_source); - // this.change_queue_file != null; - //} - this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) ); - - Variant? return_value; - - var args = this.buildDict ( - context : this.buildDict ( ///CompletionContext; - triggerKind: new GLib.Variant.int32 (triggerType) - // triggerCharacter : new GLib.Variant.string ("") - ), - textDocument : this.buildDict ( ///TextDocumentItem; - uri: new GLib.Variant.string (file.to_url()), - version : new GLib.Variant.uint64 ( (uint64) file.version) - ), - position : this.buildDict ( - line : new GLib.Variant.uint64 ( (uint) line) , - character : new GLib.Variant.uint64 ( uint.max(0, (offset -1))) - ) - ); - - GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true)); - - yield this.jsonrpc_client.call_async ( - "textDocument/completion", - args, - null, - out return_value - ); - - - //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); - var json = Json.gvariant_serialize (return_value); - - - if (json.get_node_type() == Json.NodeType.OBJECT) { - ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList; - this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) ); - GLib.debug ("LS replied with Object"); - return ret; - } - - if (json.get_node_type() != Json.NodeType.ARRAY) { - GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); - this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??"); - return ret; - - } - var ar = json.get_array(); - - for(var i = 0; i < ar.get_length(); i++ ) { - var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem; - ret.items.add( add); - - } - this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) ); - GLib.debug ("LS replied with Array"); - return ret; - - - } - //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient - public async Gee.ArrayList syntax (JsRender.JsRender file) throws GLib.Error - { - /* partial_result_token , work_done_token context = null) */ - GLib.debug("get syntax %s", file.relpath); - var ret = new Gee.ArrayList(); - //ret = null; - if (!this.isReady()) { - return ret; - } - Variant? return_value; - yield this.jsonrpc_client.call_async ( - "textDocument/documentSymbol", - this.buildDict ( - - textDocument : this.buildDict ( ///TextDocumentItem; - uri: new GLib.Variant.string (file.to_url()), - version : new GLib.Variant.uint64 ( (uint64) file.version) - ) - - ), - null, - out return_value - ); - - - GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); - var json = Json.gvariant_serialize (return_value); - - - - var ar = json.get_array(); - for(var i = 0; i < ar.get_length(); i++ ) { - var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol; - ret.add( add); - - } - return ret ; - - - - } } + } diff --git a/src/Palete/LanguageClientDummy.vala b/src/Palete/LanguageClientDummy.vala index d83ac1c9c..4d4a53d3c 100644 --- a/src/Palete/LanguageClientDummy.vala +++ b/src/Palete/LanguageClientDummy.vala @@ -10,15 +10,24 @@ namespace Palete { } - public override void initialize_server() { + public override void initialize_server() { GLib.debug("initialize dummy server"); } - public override void startServer() - { - } - public new bool isReady() - { - return false; + public override void document_open (JsRender.JsRender file) {} + public override void document_save (JsRender.JsRender file) {} + public override void document_change (JsRender.JsRender file ) {} + public override void document_change_force (JsRender.JsRender file, string contents ) {} + public override void document_close (JsRender.JsRender file) {} + public override void exit () throws GLib.Error { } + public override async void shutdown () throws GLib.Error { } + public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error { + var ret = new Lsp.CompletionList(); + return ret; + } + + public override async Gee.ArrayList syntax (JsRender.JsRender file) throws GLib.Error { + var ret = new Gee.ArrayList(); + return ret; } } diff --git a/src/Palete/LanguageClientJavascript.vala b/src/Palete/LanguageClientJavascript.vala index 98f1d53a6..8f8b35745 100644 --- a/src/Palete/LanguageClientJavascript.vala +++ b/src/Palete/LanguageClientJavascript.vala @@ -2,66 +2,81 @@ namespace Palete { public class LanguageClientJavascript : LanguageClient { + Gee.HashMap file_contents; public LanguageClientJavascript(Project.Project project) { // extend versions will proably call initialize to start and connect to server. base(project); - + this.file_contents = new Gee.HashMap(); + GLib.debug(" START JAVASCRIPT LANG SERVER"); } public override void initialize_server() { GLib.debug("initialize javascript server"); } - public override void startServer() - { - } + - - public new bool isReady() - { - return false; - } - public new void document_open (JsRender.JsRender file) + + public override void document_open (JsRender.JsRender file) { - + this.file_contents.set(file.path, file.toSourceCode()); Javascript.singleton().validate(file.toSourceCode(), file ); BuilderApplication.updateCompileResults(); } - public new void document_save (JsRender.JsRender file) + public override void document_save (JsRender.JsRender file) { + + this.file_contents.set(file.path, file.toSourceCode()); + GLib.debug("set file %s : %d chars", file.path, this.file_contents.get(file.path).length); Javascript.singleton().validate(file.toSourceCode(), file ); BuilderApplication.updateCompileResults(); } - public new void document_change (JsRender.JsRender file ) - { - Javascript.singleton().validate(file.toSourceCode(), file ); + public override void document_change_force (JsRender.JsRender file, string contents ) { + this.file_contents.set(file.path, contents); + GLib.debug("set file %s : %d chars", file.path, this.file_contents.get(file.path).length); + Javascript.singleton().validate(contents, file ); BuilderApplication.updateCompileResults(); } - - public async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error + public override void document_change (JsRender.JsRender file ) + { + this.document_change_force( file, file.toSourceCode()); + } + public override void document_close (JsRender.JsRender file) {} + public override void exit () throws GLib.Error { } + public override async void shutdown () throws GLib.Error { } + public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error { var ret = new Lsp.CompletionList(); - - - var ar = file.toSource().split("\n"); - var ln = line >= ar.length ? "" : ar[line]; - if (ln.length >= offset) { + if (this.file_contents.get(file.path) == null) { + GLib.debug("got file %s : MISSING ", file.path); return ret; } + //GLib.debug("got file %s : %s ", file.path, this.file_contents.get(file.path)); + + var ar = this.file_contents.get(file.path).split("\n"); + var ln = line >= ar.length ? "" : ar[line-1]; + if (offset-1 >= ln.length) { + GLib.debug("request for complete on line %d @ pos %d > line length %d", line, offset, (int) ln.length); + return ret; + } + GLib.debug("got Line %d:%d '%s' ", line, offset, ln); + var start = -1; - for (var i = offset; i > 0; i--) { - GLib.debug("check char %c", ln[i]); - if (ln[i].isalpha() || ln[i] == '.') { + for (var i = offset - 1; i > 0; i--) { + GLib.debug("check char %d '%c'", i, ln[i]); + if (ln[i].isalpha() || ln[i] == '.' || ln[i] == '_') { // any other allowed chars? start = i; continue; } break; } + + var complete_string = ln.substring(start, offset - start); GLib.debug("complete string = %s", complete_string); @@ -109,15 +124,32 @@ namespace Palete { var parts = complete_string.split("."); var curtype = ""; var cur_instance = false; + if (parts[0] == "_this") { + if (file.tree == null) { + + GLib.debug("file has no tree"); + return ret; // no idea.. + } + curtype = file.tree.fqn(); + cur_instance = true; + // work out from the node, what the type is... + // fetch node from element. + + //curtype = node.fqn(); + cur_instance = true; + } + + if (parts[0] == "this") { // work out from the node, what the type is... // fetch node from element. - //if (node == null) { - GLib.debug("node is empty - no return\n"); + var node = file.lineToNode(line -1); // hopefuly + if (node == null) { + GLib.debug("could nt find scope for 'this'"); return ret; // no idea.. - //} - //curtype = node.fqn(); - //cur_instance = true; + } + curtype = node.fqn(); + cur_instance = true; } if (parts[0] == "Roo") { curtype = "Roo"; @@ -132,7 +164,7 @@ namespace Palete { // look up all the properties of the type... var cls = this.project.palete.getClass(curtype); if (cls == null) { - GLib.debug("could not get class of curtype %s\n", curtype); + GLib.debug("could not get class of curtype '%s'\n", curtype); return ret; } @@ -216,7 +248,7 @@ namespace Palete { // got a matching property... // return type? - var sci = new Lsp.CompletionItem.keyword( prevbits + prop.name + "(", prop.name + "(" , prop.doctxt ); + var sci = new Lsp.CompletionItem.keyword( prop.name + "(", prop.name + "(" , prop.doctxt ); ret.items.add(sci); @@ -229,11 +261,11 @@ namespace Palete { while (citer.next()) { var prop = citer.get_value(); // does the name start with ... - if (parts[i].length > 0 && prop.name.index_of(parts[i],0) != 0) { - continue; - } + //if (parts[i].length > 0 && prop.name.index_of(parts[i],0) != 0) { + // continue; + //} - var sci = new Lsp.CompletionItem.keyword( prevbits + prop.name + "(", prop.name + "(" , prop.doctxt ); + var sci = new Lsp.CompletionItem.keyword( prop.name, prop.name , prop.doctxt ); ret.items.add(sci); @@ -256,6 +288,10 @@ namespace Palete { return ret; } + public override async Gee.ArrayList syntax (JsRender.JsRender file) throws GLib.Error { + var ret = new Gee.ArrayList(); + return ret; + } } diff --git a/src/Palete/LanguageClientVala.vala b/src/Palete/LanguageClientVala.vala index 4860da293..f35f74fdd 100644 --- a/src/Palete/LanguageClientVala.vala +++ b/src/Palete/LanguageClientVala.vala @@ -1,24 +1,141 @@ namespace Palete { public class LanguageClientVala : LanguageClient { - - + int countdown = 0; + protected bool initialized = false; + bool sent_shutdown = false; + uint change_queue_id = 0; + + + private bool _closed = false; + private bool closed { + get { return this._closed ; } + set { + GLib.debug("closed has been set? to %s" , value ? "TRUE" : "FALSE" ); + this._closed = value; + } + } + private GLib.SubprocessLauncher launcher = null; + private GLib.Subprocess? subprocess = null; + private IOStream? subprocess_stream = null; + public Jsonrpc.Client? jsonrpc_client = null; + + Gee.ArrayList open_files; + private JsRender.JsRender? _change_queue_file = null; + private string change_queue_file_source = ""; + + JsRender.JsRender? change_queue_file { + set { + this.change_queue_file_source = value == null ? "" : value.toSource(); + this._change_queue_file = value; + } + get { + return this._change_queue_file; + } + } + void startServer() + { + this.initProcess("/usr/bin/vala-language-server"); + } + + public LanguageClientVala(Project.Project project) { // extend versions will proably call initialize to start and connect to server. base(project); - + this.open_files = new Gee.ArrayList(); + this.change_queue_id = GLib.Timeout.add_seconds(1, () => { + if (this.change_queue_file == null) { + return true; + } + this.countdown--; + if (this.countdown < 0){ + this.document_change_force(this.change_queue_file, this.change_queue_file_source); + this.change_queue_file = null; + + } + return true; + }); this.startServer(); - - + } - public override void startServer() - { - this.initProcess("/usr/bin/vala-language-server"); + + public bool initProcess(string process_path) + { + this.onClose(); + this.log(LanguageClientAction.LAUNCH, process_path); + GLib.debug("Launching %s", process_path); + this.launcher = new GLib.SubprocessLauncher (SubprocessFlags.STDIN_PIPE | SubprocessFlags.STDOUT_PIPE); + this.launcher.set_environ(GLib.Environ.get()); + try { + + + this.subprocess = launcher.spawnv ({ process_path }); + + this.subprocess.wait_async.begin( null, ( obj,res ) => { + try { + this.subprocess.wait_async.end(res); + } catch (GLib.Error e) { + this.log(LanguageClientAction.ERROR_START, e.message); + GLib.debug("subprocess startup error %s", e.message); + } + this.log(LanguageClientAction.EXIT, "process ended"); + GLib.debug("Subprocess ended %s", process_path); + this.onClose(); + + }); + var input_stream = this.subprocess.get_stdout_pipe (); + var output_stream = this.subprocess.get_stdin_pipe (); + + if (input_stream is GLib.UnixInputStream && output_stream is GLib.UnixOutputStream) { + // set nonblocking + if (!GLib.Unix.set_fd_nonblocking(((GLib.UnixInputStream)input_stream).fd, true) + || !GLib.Unix.set_fd_nonblocking (((GLib.UnixOutputStream)output_stream).fd, true)) + { + GLib.debug("could not set pipes to nonblocking"); + this.onClose(); + return false; + } + } + this.subprocess_stream = new GLib.SimpleIOStream (input_stream, output_stream); + this.accept_io_stream ( this.subprocess_stream); + } catch (GLib.Error e) { + this.log(LanguageClientAction.ERROR_START, e.message); + GLib.debug("subprocess startup error %s", e.message); + this.onClose(); + return false; + } + return true; + } + bool in_close = false; + public override void client_accepted (Jsonrpc.Client client) + { + if (this.jsonrpc_client == null) { + this.jsonrpc_client = client; + + GLib.debug("client accepted connection - calling init server"); + this.log(LanguageClientAction.ACCEPT, "client accepted"); + + this.jsonrpc_client.notification.connect((method, paramz) => { + this.onNotification(method, paramz); + }); + + this.jsonrpc_client.failed.connect(() => { + this.log(LanguageClientAction.ERROR_RPC, "client failed"); + this.onClose(); + + GLib.debug("language server server has failed"); + }); + + this.initialize_server (); + } + + } - public override void initialize_server() { + + public override void initialize_server() { try { Variant? return_value; this.jsonrpc_client.call ( @@ -40,7 +157,420 @@ namespace Palete { } } + void onClose() + { + if (this.in_close) { + return; + } + if (this.launcher == null) { + return; + } + this.in_close = true; + GLib.debug("onClose called"); + + if (this.jsonrpc_client != null) { + try { + this.jsonrpc_client.close(); + } catch (GLib.Error e) { + GLib.debug("rpc Error close error %s", e.message); + } + } + if (this.subprocess_stream != null) { + try { + this.subprocess_stream.close(); + } catch (GLib.Error e) { + GLib.debug("stream Error close %s", e.message); + } + } + if (this.subprocess != null) { + this.subprocess.force_exit(); + } + if (this.launcher != null) { + this.launcher.close(); + } + + this.launcher = null; + this.subprocess = null; + this.jsonrpc_client = null; + this.closed = true; + this.in_close = false; + } + + + public bool isReady() + { + if (this.closed) { + this.log(LanguageClientAction.RESTART,"closed is set - restarting"); + GLib.debug("server stopped = restarting"); + this.initialized = false; + this.closed = false; + this.startServer(); + foreach(var f in this.open_files) { + this.document_open(f); + } + return false; // can't do an operation yet? + + } + + if (!this.initialized) { + GLib.debug("Server has not been initialized"); + return false; + } + if (this.sent_shutdown) { + GLib.debug("Server has been started its shutting down process"); + return false; + } + // restart server.. + + + + return true; + } + public void onNotification(string method, Variant? return_value) + { + switch (method) { + case "textDocument/publishDiagnostics": + this.onDiagnostic(return_value); + return; + default: + break; + + } + GLib.debug("got notification %s : %s", method , Json.to_string (Json.gvariant_serialize (return_value), true)); + + } + + /*** + + */ + public void onDiagnostic(Variant? return_value) + { + + var dg = Json.gobject_deserialize (typeof (Lsp.Diagnostics), Json.gvariant_serialize (return_value)) as Lsp.Diagnostics; + this.log(LanguageClientAction.DIAG, dg.filename); + var f = this.project.getByPath(dg.filename); + if (f == null) { + //GLib.debug("no file %s", dg.uri); + this.project.updateErrorsforFile(null); + return; + } + foreach(var v in f.errorsByType.values) { + v.remove_all(); + } + foreach(var diag in dg.diagnostics) { + var ce = new CompileError.new_from_diagnostic(f, diag); + if (!f.errorsByType.has_key(ce.category)) { + f.errorsByType.set(ce.category, new GLib.ListStore(typeof(CompileError))); + } + f.errorsByType.get(ce.category).append(ce); + } + f.project.updateErrorsforFile(f); + + } + + public override void document_open (JsRender.JsRender file) + { + if (!this.isReady()) { + return; + } + if (!this.open_files.contains(file)) { + this.open_files.add(file); + } + + GLib.debug ("LS sent open"); + try { + this.jsonrpc_client.send_notification ( + "textDocument/didOpen", + this.buildDict ( + textDocument : this.buildDict ( + uri: new Variant.string (file.to_url()), + languageId : new Variant.string (file.language_id()), + version : new GLib.Variant.uint64 ( (uint64) file.version), + text : new Variant.string (file.toSource()) + ) + ), + null + ); + this.log(LanguageClientAction.OPEN, file.path); + } catch( GLib.Error e) { + this.log(LanguageClientAction.ERROR_RPC, e.message); + this.onClose(); + GLib.debug ("LS sent open err %s", e.message); + } + + } + + public override void document_save (JsRender.JsRender file) + { + if (!this.isReady()) { + return; + } + this.change_queue_file = null; + GLib.debug ("LS send save"); + try { + this.jsonrpc_client.send_notification ( + "textDocument/didChange", + this.buildDict ( + textDocument : this.buildDict ( ///TextDocumentItem; + uri: new GLib.Variant.string (file.to_url()), + version : new GLib.Variant.uint64 ( (uint64) file.version) + ) + ), + null + ); + this.log(LanguageClientAction.SAVE, file.path); + } catch( GLib.Error e) { + this.log(LanguageClientAction.ERROR_RPC, e.message); + GLib.debug ("LS save err %s", e.message); + this.onClose(); + } + + + } + public override void document_close (JsRender.JsRender file) + { + if (!this.isReady()) { + return; + } + this.change_queue_file = null; + + if (this.open_files.contains(file)) { + this.open_files.remove(file); + } + this.log(LanguageClientAction.CLOSE, file.path); + GLib.debug ("LS send close"); + try { + this.jsonrpc_client.send_notification ( + "textDocument/didChange", + this.buildDict ( + textDocument : this.buildDict ( ///TextDocumentItem; + uri: new GLib.Variant.string (file.to_url()) + + ) + ), + null + ); + } catch( GLib.Error e) { + this.log(LanguageClientAction.ERROR_RPC, e.message); + GLib.debug ("LS close err %s", e.message); + this.onClose(); + } + + + } + + + public override void document_change (JsRender.JsRender file ) + { + if (this.change_queue_file != null && this.change_queue_file.path != file.path) { + this.document_change_force(this.change_queue_file, this.change_queue_file_source); + } + + this.countdown = 3; + this.change_queue_file = file; + + + + } + + + public override void document_change_force (JsRender.JsRender file, string contents) + { + if (!this.isReady()) { + return; + } + + + GLib.debug ("LS send change"); + var ar = new Json.Array(); + var obj = new Json.Object(); + obj.set_string_member("text", contents); + ar.add_object_element(obj); + var node = new Json.Node(Json.NodeType.ARRAY); + node.set_array(ar); + this.log(LanguageClientAction.CHANGE, file.path); + try { + this.jsonrpc_client.send_notification ( + "textDocument/didChange", + this.buildDict ( + textDocument : this.buildDict ( ///TextDocumentItem; + uri: new GLib.Variant.string (file.to_url()), + version : new GLib.Variant.uint64 ( (uint64) file.version) + ), + contentChanges : Json.gvariant_deserialize (node, null) + + ), + null + ); + } catch( GLib.Error e) { + this.log(LanguageClientAction.ERROR_RPC, e.message); + GLib.debug ("LS change err %s", e.message); + this.onClose(); + } + + + } + // called by close window (on last window)... + public override void exit () throws GLib.Error + { + if (!this.isReady()) { + + return; + } + this.log(LanguageClientAction.TERM, "SEND exit"); + + this.jsonrpc_client.send_notification ( + "exit", + null, + null + ); + this.onClose(); + + } + // not used currently.. + public override async void shutdown () throws GLib.Error + { + if (!this.isReady()) { + return; + } + this.log(LanguageClientAction.TERM, "SEND shutodwn"); + this.sent_shutdown = true; + Variant? return_value; + yield this.jsonrpc_client.call_async ( + "shutdown", + null, + null, + out return_value + ); + GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); + } + //public async ??/symbol (string symbol) throws GLib.Error { + + // and now for the important styff.. + + /* + + @triggerType 1 = typing or ctl-spac, 2 = tiggercharactres? 3= inside completion? + */ + public override async Lsp.CompletionList? completion(JsRender.JsRender file, int line, int offset , int triggerType = 1) throws GLib.Error + { + /* partial_result_token , work_done_token context = null) */ + GLib.debug("%s get completion %s @ %d:%d", this.get_type().name(), file.relpath, line, offset); + + var ret = new Lsp.CompletionList(); + + if (!this.isReady()) { + GLib.debug("completion - language server not ready"); + return ret; + } + // make sure completion has the latest info.. + //if (this.change_queue_file != null && this.change_queue_file.path != file.path) { + // this.document_change_real(this.change_queue_file, this.change_queue_file_source); + // this.change_queue_file != null; + //} + this.log(LanguageClientAction.COMPLETE, "SEND complete %s @ %d:%d".printf(file.relpath, line, offset) ); + + Variant? return_value; + + var args = this.buildDict ( + context : this.buildDict ( ///CompletionContext; + triggerKind: new GLib.Variant.int32 (triggerType) + // triggerCharacter : new GLib.Variant.string ("") + ), + textDocument : this.buildDict ( ///TextDocumentItem; + uri: new GLib.Variant.string (file.to_url()), + version : new GLib.Variant.uint64 ( (uint64) file.version) + ), + position : this.buildDict ( + line : new GLib.Variant.uint64 ( (uint) line) , + character : new GLib.Variant.uint64 ( uint.max(0, (offset -1))) + ) + ); + + GLib.debug ("textDocument/completion send with %s", Json.to_string (Json.gvariant_serialize (args), true)); + + yield this.jsonrpc_client.call_async ( + "textDocument/completion", + args, + null, + out return_value + ); + + + //GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); + var json = Json.gvariant_serialize (return_value); + + + if (json.get_node_type() == Json.NodeType.OBJECT) { + ret = Json.gobject_deserialize (typeof (Lsp.CompletionList), json) as Lsp.CompletionList; + this.log(LanguageClientAction.COMPLETE_REPLY, "GOT complete %d items".printf(ret.items.size) ); + GLib.debug ("LS replied with Object"); + return ret; + } + + if (json.get_node_type() != Json.NodeType.ARRAY) { + GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); + this.log(LanguageClientAction.ERROR_REPLY, "GOT something else??"); + return ret; + + } + var ar = json.get_array(); + + for(var i = 0; i < ar.get_length(); i++ ) { + var add= Json.gobject_deserialize ( typeof (Lsp.CompletionItem), ar.get_element(i)) as Lsp.CompletionItem; + ret.items.add( add); + + } + this.log(LanguageClientAction.COMPLETE_REPLY, "GOT array %d items".printf(ret.items.size) ); + GLib.debug ("LS replied with Array"); + return ret; + + + } + //CompletionListInfo.itmems.parse_varient or CompletionListInfo.parsevarient + public override async Gee.ArrayList syntax (JsRender.JsRender file) throws GLib.Error + { + /* partial_result_token , work_done_token context = null) */ + GLib.debug("get syntax %s", file.relpath); + var ret = new Gee.ArrayList(); + //ret = null; + if (!this.isReady()) { + return ret; + } + Variant? return_value; + yield this.jsonrpc_client.call_async ( + "textDocument/documentSymbol", + this.buildDict ( + + textDocument : this.buildDict ( ///TextDocumentItem; + uri: new GLib.Variant.string (file.to_url()), + version : new GLib.Variant.uint64 ( (uint64) file.version) + ) + + ), + null, + out return_value + ); + + + GLib.debug ("LS replied with %s", Json.to_string (Json.gvariant_serialize (return_value), true)); + var json = Json.gvariant_serialize (return_value); + + + + var ar = json.get_array(); + for(var i = 0; i < ar.get_length(); i++ ) { + var add= Json.gobject_deserialize ( typeof (Lsp.DocumentSymbol), ar.get_element(i)) as Lsp.DocumentSymbol; + ret.add( add); + + } + return ret ; + + + + } + } } \ No newline at end of file diff --git a/src/Project/Roo.vala b/src/Project/Roo.vala index 2cae18f23..886358647 100644 --- a/src/Project/Roo.vala +++ b/src/Project/Roo.vala @@ -88,20 +88,24 @@ public class Project.Roo : Project { public override Palete.LanguageClient getLanguageServer(string lang) { - switch(lang) { - case "javascript": - var ls = new Palete.LanguageClientJavascript(this); - ls.log.connect((act, msg) => { - //GLib.debug("log %s: %s", act.to_string(), msg); - BuilderApplication.showSpinnerLspLog(act,msg); - }); - this.language_servers.set(lang, ls); - break; - - default: - return this.language_servers.get("dummy"); - } - return this.language_servers.get(lang); + if (this.language_servers.has_key(lang)) { + return this.language_servers.get(lang); + } + + switch(lang) { + case "javascript": + var ls = new Palete.LanguageClientJavascript(this); + ls.log.connect((act, msg) => { + //GLib.debug("log %s: %s", act.to_string(), msg); + BuilderApplication.showSpinnerLspLog(act,msg); + }); + this.language_servers.set(lang, ls); + break; + + default: + return this.language_servers.get("dummy"); + } + return this.language_servers.get(lang); } } -- 2.39.2