From b79691c56c6f066c9cc3bdb731cb9f67dff2d218 Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Sun, 10 Sep 2023 15:31:03 +0200 Subject: [PATCH 01/11] feat: adapt hash-table getter functions to work on server-config node --- lsp-docker.el | 57 +++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index 14c69bc..b45ad0d 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -308,11 +308,10 @@ be bigger than default servers in order to override them)") (ht ('docker (list 'container 'image))) "A list of all supported server types and subtypes, currently only docker is supported") -(defun lsp-docker-get-server-type-subtype (config) - "Get the server type" - (let* ((lsp-server-info (gethash 'server config)) - (lsp-server-type (gethash 'type lsp-server-info)) - (lsp-server-subtype (gethash 'subtype lsp-server-info))) +(defun lsp-docker-get-server-type-subtype (server-config) + "Get the server type & sub-type from the SERVER-CONFIG hash-table" + (let* ((lsp-server-type (gethash 'type server-config)) + (lsp-server-subtype (gethash 'subtype server-config))) (cons (if (stringp lsp-server-type) (intern lsp-server-type) lsp-server-type) @@ -320,31 +319,28 @@ be bigger than default servers in order to override them)") (intern lsp-server-subtype) lsp-server-subtype)))) -(defun lsp-docker-get-server-container-name (config) - "Get the server container name" - (let* ((lsp-server-info (gethash 'server config)) - (lsp-server-subtype (gethash 'subtype lsp-server-info))) +(defun lsp-docker-get-server-container-name (server-config) + "Get the server container name from the SERVER-CONFIG hash-table" + (let* ((lsp-server-subtype (gethash 'subtype server-config))) (if (equal lsp-server-subtype "container") - (gethash 'name lsp-server-info)))) + (gethash 'name server-config)))) -(defun lsp-docker-get-server-image-name (config) - "Get the server image name" - (let* ((lsp-server-info (gethash 'server config)) - (lsp-server-subtype (gethash 'subtype lsp-server-info))) +(defun lsp-docker-get-server-image-name (server-config) + "Get the server image name from the SERVER-CONFIG hash-table" + (let* ((lsp-server-subtype (gethash 'subtype server-config))) (if (equal lsp-server-subtype "image") - (gethash 'name lsp-server-info)))) - -(defun lsp-docker-get-server-id (config) - "Get the server id" - (let ((lsp-server-info (gethash 'server config))) - (if (stringp (gethash 'server lsp-server-info)) - (intern (gethash 'server lsp-server-info)) - (gethash 'server lsp-server-info)))) - -(defun lsp-docker--get-base-client (config) - "Get the base lsp client for dockerized client to be built upon" - (if-let* ((base-server-id (lsp-docker-get-server-id config)) - (base-client (gethash base-server-id lsp-clients))) + (gethash 'name server-config)))) + +(defun lsp-docker-get-server-id (server-config) + "Get the server id from the SERVER-CONFIG hash-table" + (if (stringp (gethash 'server server-config)) + (intern (gethash 'server server-config)) + (gethash 'server server-config))) + +(defun lsp-docker--get-base-client (base-server-id) + "Get the base lsp client associated to BASE-SERVER-ID key for +dockerized client to be built upon" + (if-let* ((base-client (gethash base-server-id lsp-clients))) base-client (user-error "Cannot find a specified base lsp client! Make sure the 'server' sub-key is set to one of the lsp registered clients"))) @@ -358,10 +354,9 @@ Make sure the 'server' sub-key is set to one of the lsp registered clients"))) lsp-mappings-info) (user-error "No path mappings specified!"))) -(defun lsp-docker-get-launch-command (config) - "Get the server launch command" - (let ((lsp-server-info (gethash 'server config))) - (gethash 'launch_command lsp-server-info))) +(defun lsp-docker-get-launch-command (server-config) + "Get the server launch command from the SERVER-CONFIG hash-table" + (gethash 'launch_command server-config)) (defun lsp-docker-check-server-type-subtype (supported-server-types-subtypes server-type-subtype) "Verify that the combination of server (type . subtype) is supported by the current implementation" From 88487189a61268f04f8612e170bee45ace0fdee5 Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Sun, 10 Sep 2023 15:37:29 +0200 Subject: [PATCH 02/11] feat: add support for multi-server configuration --- lsp-docker.el | 138 +++++++++++++++++++++++++++++++------------------- 1 file changed, 87 insertions(+), 51 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index b45ad0d..e7b2249 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -622,65 +622,101 @@ output)" (message "Registered a language server with id: %s and container name: %s" docker-server-id docker-container-name)) (user-error "No such client %s" server-id))) +(defun lsp-docker--register-single-server (server-config project-root path-mappings) + "Register a single dockerized language server. + +Its description is provided via the SERVER-CONFIG hash-table. It +must represents the fields defined under the `server' (single +server configuration) or `servers/' +(multi-server configuration) node. The PROJECT-ROOT must be a +path pointing to the top-level folder of the project the +configuration file resides into. The PATH-MAPPINGS provides a +hash-table to translate the paths between the host and the +dockerized server." + (let* ((server-type-subtype (lsp-docker-get-server-type-subtype server-config)) + (config-specified-server-container-name (lsp-docker-get-server-container-name server-config)) + (server-image-name (lsp-docker-get-server-image-name server-config)) + (regular-server-id (lsp-docker-get-server-id server-config)) + (server-id (lsp-docker-generate-docker-server-id server-config (lsp-workspace-root))) + (server-launch-command (lsp-docker-get-launch-command server-config)) + (base-client (lsp-docker--get-base-client server-id)) + (activation-fn (lsp-docker--create-activation-function-by-project-dir-and-base-client + (lsp-workspace-root) + base-client)) + (server-container-name (lsp-docker--finalize-docker-server-container-name + config-specified-server-container-name server-config project-root))) + + (if (and (lsp-docker-check-server-type-subtype lsp-docker-supported-server-types-subtypes + server-type-subtype) + (lsp-docker-check-path-mappings path-mappings)) + (let ((container-type (car server-type-subtype)) + (container-subtype (cdr server-type-subtype))) + (pcase container-type + ('docker (pcase container-subtype + ('image (if (lsp-docker--check-image-exists server-image-name) + (lsp-docker-register-client-with-activation-fn + :server-id regular-server-id + :docker-server-id server-id + :path-mappings path-mappings + :docker-image-id server-image-name + :docker-container-name server-container-name + :activation-fn activation-fn + :priority lsp-docker-default-priority + :server-command server-launch-command + :launch-server-cmd-fn #'lsp-docker-launch-new-container) + (lsp-docker--build-image-and-register-server-async + :image-name server-image-name + :dockerfile-path (lsp-docker--find-project-dockerfile-from-lsp) + :server-id regular-server-id + :docker-server-id server-id + :path-mappings path-mappings + :docker-container-name server-container-name + :activation-fn activation-fn + :server-command server-launch-command))) + ('container (if (lsp-docker--check-container-exists server-container-name) + (lsp-docker-register-client-with-activation-fn + :server-id regular-server-id + :docker-server-id server-id + :path-mappings path-mappings + :docker-image-id nil + :docker-container-name server-container-name + :activation-fn activation-fn + :priority lsp-docker-default-priority + :server-command server-launch-command + :launch-server-cmd-fn #'lsp-docker-launch-existing-container) + (user-error "Invalid LSP docker config: cannot find the specified container: %s" server-container-name))) + (user-error "Invalid LSP docker config: unsupported server type and/or subtype"))) + (user-error "Invalid LSP docker config: unsupported server type and/or subtype"))) + (user-error "Language server registration failed, check input parameters")))) + (defun lsp-docker-register () "Register one or more dockerized language servers for the current project" (interactive) (if (lsp-workspace-root) (let* ( (config (lsp-docker-get-config-from-lsp)) - (dockerfile-path (lsp-docker--find-project-dockerfile-from-lsp)) (project-root (lsp-workspace-root)) - (server-type-subtype (lsp-docker-get-server-type-subtype config)) - (config-specified-server-container-name (lsp-docker-get-server-container-name config)) - (server-image-name (lsp-docker-get-server-image-name config)) (path-mappings (lsp-docker-get-path-mappings config (lsp-workspace-root))) - (regular-server-id (lsp-docker-get-server-id config)) - (server-id (lsp-docker-generate-docker-server-id config (lsp-workspace-root))) - (server-launch-command (lsp-docker-get-launch-command config)) - (base-client (lsp-docker--get-base-client config)) - (server-container-name (lsp-docker--finalize-docker-server-container-name config-specified-server-container-name config project-root))) - (if (and (lsp-docker-check-server-type-subtype lsp-docker-supported-server-types-subtypes - server-type-subtype) - (lsp-docker-check-path-mappings path-mappings)) - (let ((container-type (car server-type-subtype)) - (container-subtype (cdr server-type-subtype))) - (pcase container-type - ('docker (pcase container-subtype - ('image (if (lsp-docker--check-image-exists server-image-name) - (lsp-docker-register-client-with-activation-fn - :server-id regular-server-id - :docker-server-id server-id - :path-mappings path-mappings - :docker-image-id server-image-name - :docker-container-name server-container-name - :activation-fn (lsp-docker--create-activation-function-by-project-dir-and-base-client (lsp-workspace-root) base-client) - :priority lsp-docker-default-priority - :server-command server-launch-command - :launch-server-cmd-fn #'lsp-docker-launch-new-container) - (lsp-docker--build-image-and-register-server-async - :image-name server-image-name - :dockerfile-path dockerfile-path - :server-id regular-server-id - :docker-server-id server-id - :path-mappings path-mappings - :docker-container-name server-container-name - :activation-fn (lsp-docker--create-activation-function-by-project-dir-and-base-client (lsp-workspace-root) base-client) - :server-command server-launch-command))) - ('container (if (lsp-docker--check-container-exists server-container-name) - (lsp-docker-register-client-with-activation-fn - :server-id regular-server-id - :docker-server-id server-id - :path-mappings path-mappings - :docker-image-id nil - :docker-container-name server-container-name - :activation-fn (lsp-docker--create-activation-function-by-project-dir-and-base-client (lsp-workspace-root) base-client) - :priority lsp-docker-default-priority - :server-command server-launch-command - :launch-server-cmd-fn #'lsp-docker-launch-existing-container) - (user-error "Invalid LSP docker config: cannot find the specified container: %s" server-container-name))) - (user-error "Invalid LSP docker config: unsupported server type and/or subtype"))) - (user-error "Invalid LSP docker config: unsupported server type and/or subtype"))) - (user-error "Language server registration failed, check input parameters"))) + (single-server-config (gethash 'server config)) + (multi-server-config (gethash 'multi-server config))) + + ;; check whether a single or multiple servers are described in the configuration + (cond + (single-server-config + (message "registering a single server") + (lsp-docker--register-single-server single-server-config project-root path-mappings)) + + (multi-server-config + (message "registering multiple servers") + (--map (lsp-docker--register-single-server + ;; get per-dockerized-server configuration + (gethash it multi-server-config) + project-root + path-mappings) + (lsp-docker--get-ht-keys multi-server-config))) + (t + (user-error "no 'server' neither 'multi-server' keyword found in configuration file"))) + ) (user-error (format "Current file: %s is not in a registered project!" (buffer-file-name))))) (defun lsp-docker-start () From 55328d4ae7e508b9fa364054c21fe61f3444ba78 Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Thu, 14 Sep 2023 23:12:24 +0200 Subject: [PATCH 03/11] fix: correct argument to get base client instance, style fix --- lsp-docker.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index e7b2249..8a71b09 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -639,7 +639,7 @@ dockerized server." (regular-server-id (lsp-docker-get-server-id server-config)) (server-id (lsp-docker-generate-docker-server-id server-config (lsp-workspace-root))) (server-launch-command (lsp-docker-get-launch-command server-config)) - (base-client (lsp-docker--get-base-client server-id)) + (base-client (lsp-docker--get-base-client regular-server-id)) (activation-fn (lsp-docker--create-activation-function-by-project-dir-and-base-client (lsp-workspace-root) base-client)) @@ -715,8 +715,7 @@ dockerized server." path-mappings) (lsp-docker--get-ht-keys multi-server-config))) (t - (user-error "no 'server' neither 'multi-server' keyword found in configuration file"))) - ) + (user-error "no 'server' neither 'multi-server' keyword found in configuration file")))) (user-error (format "Current file: %s is not in a registered project!" (buffer-file-name))))) (defun lsp-docker-start () From 2404c48ef42ae262b92ab5a7e10060d6555889b3 Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Sat, 16 Sep 2023 13:40:07 +0200 Subject: [PATCH 04/11] chores: complete "config" to "server-config" renaming, improve function description text --- lsp-docker.el | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index 8a71b09..8deff02 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -346,7 +346,7 @@ dockerized client to be built upon" Make sure the 'server' sub-key is set to one of the lsp registered clients"))) (defun lsp-docker-get-path-mappings (config project-directory) - "Get the server path mappings" + "Get the server path mappings from the top project hash-table CONFIG" (if-let ((lsp-mappings-info (gethash 'mappings config))) (--map (cons (f-canonical (f-expand (gethash 'source it) project-directory)) @@ -404,23 +404,28 @@ Argument DOCKER-CONTAINER-NAME name to use for container." nil) (-contains? base-major-modes current-major-mode)))))) -(defun lsp-docker-generate-docker-server-id (config project-root) - "Generate the docker-server-id from the project config" - (let ((original-server-id (symbol-name (lsp-docker-get-server-id config))) +(defun lsp-docker-generate-docker-server-id (server-config project-root) + "Generate the docker-server-id from the SERVER-CONFIG" + (let ((original-server-id (symbol-name (lsp-docker-get-server-id server-config))) (project-path-server-id-part (s-chop-prefix "-" (s-replace-all '(("/" . "-") ("." . "")) project-root)))) (intern (s-join "-" (list project-path-server-id-part original-server-id "docker"))))) -(defun lsp-docker--generate-docker-server-container-name (config project-root) - "Generate the docker-container-name from the project config" - (let ((docker-server-id (lsp-docker-generate-docker-server-id config project-root))) +(defun lsp-docker--generate-docker-server-container-name (server-config project-root) + "Generate the docker-container-name from the SERVER-CONFIG" + (let ((docker-server-id (lsp-docker-generate-docker-server-id server-config project-root))) (if (symbolp docker-server-id) (symbol-name docker-server-id) docker-server-id))) -(defun lsp-docker--finalize-docker-server-container-name (config-specified-server-name config project-root) - "Get or generate a unique (if generated) or leave config-specified server name" +(defun lsp-docker--finalize-docker-server-container-name (config-specified-server-name server-config project-root) + "Get or generate the container name. + +If CONFIG-SPECIFIED-SERVER-NAME is non-nil, return it as +container name. Otherwise generate a unique container name from +SERVER-CONFIG and PROJECT-ROOT. +" (cond ((stringp config-specified-server-name) config-specified-server-name) - ('t (lsp-docker--attach-container-name-global-suffix (lsp-docker--generate-docker-server-container-name config project-root))))) + ('t (lsp-docker--attach-container-name-global-suffix (lsp-docker--generate-docker-server-container-name server-config project-root))))) (defun lsp-docker--encode-single-quoted-parameters (raw-token-command) "Encode single quoted tokens (with base64 encoding) so they won't be split" From 0b5450ce76a141bd67fb854046874fbe61e7de4e Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Mon, 23 Oct 2023 21:46:47 +0200 Subject: [PATCH 05/11] feat: add error messages, correct some doc-strings --- lsp-docker.el | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index 8deff02..2219ae7 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -301,8 +301,10 @@ be bigger than default servers in order to override them)") (defun lsp-docker-get-config-from-lsp () "Get the LSP configuration based on a project-local configuration (using lsp-mode)" (let ((project-config-file-path (lsp-docker--find-project-config-file-from-lsp))) - (or (lsp-docker-get-config-from-project-config-file project-config-file-path) - (ht-copy lsp-docker-persistent-default-config)))) + (if project-config-file-path + (or (lsp-docker-get-config-from-project-config-file project-config-file-path) + (ht-copy lsp-docker-persistent-default-config)) + (user-error "cannot find LSP configuration file project, refer to the documentation")))) (defvar lsp-docker-supported-server-types-subtypes (ht ('docker (list 'container 'image))) @@ -632,7 +634,7 @@ output)" Its description is provided via the SERVER-CONFIG hash-table. It must represents the fields defined under the `server' (single -server configuration) or `servers/' +server configuration) or `multi-server/' (multi-server configuration) node. The PROJECT-ROOT must be a path pointing to the top-level folder of the project the configuration file resides into. The PATH-MAPPINGS provides a @@ -708,6 +710,12 @@ dockerized server." ;; check whether a single or multiple servers are described in the configuration (cond (single-server-config + (if (and single-server-config multi-server-config) + (display-warning "lsp-docker" + (concat "both single/multiple server configuration detected, " + "ignoring multi-server configuration") + :warning + "*Warnings lsp-docker*")) (message "registering a single server") (lsp-docker--register-single-server single-server-config project-root path-mappings)) @@ -720,8 +728,11 @@ dockerized server." path-mappings) (lsp-docker--get-ht-keys multi-server-config))) (t - (user-error "no 'server' neither 'multi-server' keyword found in configuration file")))) - (user-error (format "Current file: %s is not in a registered project!" (buffer-file-name))))) + (user-error "no 'server' neither 'multi-server' keywords found in configuration file")))) + (user-error + (format (concat "Current file: %s is not in a registered project! " + "Try adding your project with `lsp-workspace-folders-add'") + (buffer-file-name))))) (defun lsp-docker-start () "Register and launch a server to use LSP mode in a container for the current project" From c5ac2af85533b5f5a3455370a0ab8dabd1dbd40e Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Mon, 23 Oct 2023 22:08:44 +0200 Subject: [PATCH 06/11] feat: use defconst for 'server and 'multi-server keywords, fix configuration merging --- lsp-docker.el | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index 2219ae7..1c4e27b 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -49,6 +49,12 @@ :group 'lsp-docker :type 'string) +(defconst lsp-docker--single-server-yml-key 'server + "single-server YAML key, if matched it is assumed a single language server is being configured") + +(defconst lsp-docker--multi-server-yml-key 'multi-server + "multi-server YAML key, if matched it is assumed multiple language servers are being configured") + (defun lsp-docker--log-docker-supplemental-calls-p () "Return non-nil if should log docker invocation commands" lsp-docker-log-docker-supplemental-calls) @@ -252,14 +258,14 @@ the docker container to run the language server." be bigger than default servers in order to override them)") (defcustom lsp-docker-persistent-default-config - (ht ('server (ht ('type "docker") - ('subtype "image") - ('name "emacslsp/lsp-docker-langservers") - ('server nil) - ('launch_command nil))) + (ht (lsp-docker--single-server-yml-key (ht ('type "docker") + ('subtype "image") + ('name "emacslsp/lsp-docker-langservers") + ('server nil) + ('launch_command nil))) ('mappings (vector (ht ('source ".") - ('destination "/projects"))))) + ('destination "/projects"))))) "Default configuration for all language servers with persistent configurations" :type 'hash-table :group 'lsp-docker) @@ -268,8 +274,13 @@ be bigger than default servers in order to override them)") "Get the LSP configuration based on a project configuration file" (if (f-exists? project-config-file-path) (if-let* ((whole-config (yaml-parse-string (f-read project-config-file-path))) - (lsp-config (gethash 'lsp whole-config))) - (ht-merge (ht-copy lsp-docker-persistent-default-config) lsp-config)))) + (lsp-config (gethash 'lsp whole-config))) + (cond + ;; DO NOT merge to the persistent configuration when a multi-server one is detected + ((gethash lsp-docker--multi-server-yml-key lsp-config) + lsp-config) + (t ; use default values for missing fields in the provided configuration + (ht-merge (ht-copy lsp-docker-persistent-default-config) lsp-config)))))) (defun lsp-docker--find-project-config-file-from-lsp () "Get the LSP configuration file path (project-local configuration, using lsp-mode)" @@ -335,9 +346,9 @@ be bigger than default servers in order to override them)") (defun lsp-docker-get-server-id (server-config) "Get the server id from the SERVER-CONFIG hash-table" - (if (stringp (gethash 'server server-config)) - (intern (gethash 'server server-config)) - (gethash 'server server-config))) + (if (stringp (gethash lsp-docker--single-server-yml-key server-config)) + (intern (gethash lsp-docker--single-server-yml-key server-config)) + (gethash lsp-docker--single-server-yml-key server-config))) (defun lsp-docker--get-base-client (base-server-id) "Get the base lsp client associated to BASE-SERVER-ID key for @@ -345,7 +356,8 @@ dockerized client to be built upon" (if-let* ((base-client (gethash base-server-id lsp-clients))) base-client (user-error "Cannot find a specified base lsp client! -Make sure the 'server' sub-key is set to one of the lsp registered clients"))) +Make sure the '%s' sub-key is set to one of the lsp registered clients" + lsp-docker--single-server-yml-key))) (defun lsp-docker-get-path-mappings (config project-directory) "Get the server path mappings from the top project hash-table CONFIG" @@ -704,8 +716,8 @@ dockerized server." (config (lsp-docker-get-config-from-lsp)) (project-root (lsp-workspace-root)) (path-mappings (lsp-docker-get-path-mappings config (lsp-workspace-root))) - (single-server-config (gethash 'server config)) - (multi-server-config (gethash 'multi-server config))) + (single-server-config (gethash lsp-docker--single-server-yml-key config)) + (multi-server-config (gethash lsp-docker--multi-server-yml-key config))) ;; check whether a single or multiple servers are described in the configuration (cond @@ -726,10 +738,12 @@ dockerized server." (gethash it multi-server-config) project-root path-mappings) - (lsp-docker--get-ht-keys multi-server-config))) + (ht-keys multi-server-config))) (t - (user-error "no 'server' neither 'multi-server' keywords found in configuration file")))) - (user-error + (user-error "no '%s' neither '%s' keywords found in configuration file" + lsp-docker--single-server-yml-key + lsp-docker--multi-server-yml-key)))) + (user-error (format (concat "Current file: %s is not in a registered project! " "Try adding your project with `lsp-workspace-folders-add'") (buffer-file-name))))) From 73a1b8bad3d6f4dbc0d7913df1f9de47bd1c2670 Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Sat, 9 Dec 2023 16:14:05 +0100 Subject: [PATCH 07/11] feat: encode all keywords into 'defconst' - replace all <'key> instances with their dedicated 'defconst' - distinguish between 'server top-node and 'server key --- lsp-docker.el | 52 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index 1c4e27b..6b3d56f 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -49,8 +49,28 @@ :group 'lsp-docker :type 'string) -(defconst lsp-docker--single-server-yml-key 'server - "single-server YAML key, if matched it is assumed a single language server is being configured") +(defconst lsp-docker--server-key 'server + "LSP sub-key holding a single (or a group of) server(s)") + +;; supported keys in YAML configuration file(s) +(defconst lsp-docker--srv-cfg-type-key 'type + "The type of server (at the moment only `docker' is supported).") +(defconst lsp-docker--srv-cfg-subtype-key 'subtype + "For type container it can be: +- `container': attach to an already running container +- `image': when the image does not exist, try to build it based on the dockerfile + found in the project-scope An image might feature an optional tag, i.e. + `:'. the If a tagless image is indicated `latest' will be assumed") +(defconst lsp-docker--srv-cfg-name-key 'name + "Depending on the `lsp-docker--srv-cfg-subtype-key' it holds the +name of the container/image for the described language server.") +(defconst lsp-docker--srv-cfg-server-key 'server + "Server ID of a registered LSP server. You can find the list of +registered servers evaluating: `(ht-keys lsp-clients)'.") +(defconst lsp-docker--srv-cfg-launch-command-key 'launch_command + "Command to launch the language server in stdio mode. This key is +not used when the `lsp-docker--srv-cfg-subtype-key' is set to +container, as the server command shall be the entrypoint.") (defconst lsp-docker--multi-server-yml-key 'multi-server "multi-server YAML key, if matched it is assumed multiple language servers are being configured") @@ -258,11 +278,11 @@ the docker container to run the language server." be bigger than default servers in order to override them)") (defcustom lsp-docker-persistent-default-config - (ht (lsp-docker--single-server-yml-key (ht ('type "docker") - ('subtype "image") - ('name "emacslsp/lsp-docker-langservers") - ('server nil) - ('launch_command nil))) + (ht (lsp-docker--server-key (ht (lsp-docker--srv-cfg-type-key "docker") + (lsp-docker--srv-cfg-subtype-key "image") + (lsp-docker--srv-cfg-name-key "emacslsp/lsp-docker-langservers") + (lsp-docker--srv-cfg-server-key nil) + ('launch_command nil))) ('mappings (vector (ht ('source ".") ('destination "/projects"))))) @@ -323,8 +343,8 @@ be bigger than default servers in order to override them)") (defun lsp-docker-get-server-type-subtype (server-config) "Get the server type & sub-type from the SERVER-CONFIG hash-table" - (let* ((lsp-server-type (gethash 'type server-config)) - (lsp-server-subtype (gethash 'subtype server-config))) + (let* ((lsp-server-type (gethash lsp-docker--srv-cfg-type-key server-config)) + (lsp-server-subtype (gethash lsp-docker--srv-cfg-subtype-key server-config))) (cons (if (stringp lsp-server-type) (intern lsp-server-type) lsp-server-type) @@ -346,9 +366,9 @@ be bigger than default servers in order to override them)") (defun lsp-docker-get-server-id (server-config) "Get the server id from the SERVER-CONFIG hash-table" - (if (stringp (gethash lsp-docker--single-server-yml-key server-config)) - (intern (gethash lsp-docker--single-server-yml-key server-config)) - (gethash lsp-docker--single-server-yml-key server-config))) + (if (stringp (gethash lsp-docker--srv-cfg-server-key server-config)) + (intern (gethash lsp-docker--srv-cfg-server-key server-config)) + (gethash lsp-docker--srv-cfg-server-key server-config))) (defun lsp-docker--get-base-client (base-server-id) "Get the base lsp client associated to BASE-SERVER-ID key for @@ -356,8 +376,8 @@ dockerized client to be built upon" (if-let* ((base-client (gethash base-server-id lsp-clients))) base-client (user-error "Cannot find a specified base lsp client! -Make sure the '%s' sub-key is set to one of the lsp registered clients" - lsp-docker--single-server-yml-key))) +Make sure the '%s' sub-key is set to one of the lsp registered clients:\n\n%s" + lsp-docker--srv-cfg-type-key (ht-keys lsp-clients)))) (defun lsp-docker-get-path-mappings (config project-directory) "Get the server path mappings from the top project hash-table CONFIG" @@ -716,7 +736,7 @@ dockerized server." (config (lsp-docker-get-config-from-lsp)) (project-root (lsp-workspace-root)) (path-mappings (lsp-docker-get-path-mappings config (lsp-workspace-root))) - (single-server-config (gethash lsp-docker--single-server-yml-key config)) + (single-server-config (gethash lsp-docker--server-key config)) (multi-server-config (gethash lsp-docker--multi-server-yml-key config))) ;; check whether a single or multiple servers are described in the configuration @@ -741,7 +761,7 @@ dockerized server." (ht-keys multi-server-config))) (t (user-error "no '%s' neither '%s' keywords found in configuration file" - lsp-docker--single-server-yml-key + lsp-docker--server-key lsp-docker--multi-server-yml-key)))) (user-error (format (concat "Current file: %s is not in a registered project! " From 1a5177859a64a3012c2b39534abed853468fbb61 Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Sat, 9 Dec 2023 16:18:44 +0100 Subject: [PATCH 08/11] feat: merge 'lsp-docker-get-server-container-name' and 'lsp-docker-get-server-image-name' functions, improve error message --- lsp-docker.el | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index 6b3d56f..7d62d93 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -352,17 +352,9 @@ be bigger than default servers in order to override them)") (intern lsp-server-subtype) lsp-server-subtype)))) -(defun lsp-docker-get-server-container-name (server-config) - "Get the server container name from the SERVER-CONFIG hash-table" - (let* ((lsp-server-subtype (gethash 'subtype server-config))) - (if (equal lsp-server-subtype "container") - (gethash 'name server-config)))) - -(defun lsp-docker-get-server-image-name (server-config) - "Get the server image name from the SERVER-CONFIG hash-table" - (let* ((lsp-server-subtype (gethash 'subtype server-config))) - (if (equal lsp-server-subtype "image") - (gethash 'name server-config)))) +(defun lsp-docker-get-server-container/image-name (server-config) + "Get the server container/image name from the SERVER-CONFIG hash-table" + (gethash lsp-docker--srv-cfg-name-key server-config)) (defun lsp-docker-get-server-id (server-config) "Get the server id from the SERVER-CONFIG hash-table" @@ -375,9 +367,9 @@ be bigger than default servers in order to override them)") dockerized client to be built upon" (if-let* ((base-client (gethash base-server-id lsp-clients))) base-client - (user-error "Cannot find a specified base lsp client! + (user-error "Cannot find the specified base lsp client (%s)! Make sure the '%s' sub-key is set to one of the lsp registered clients:\n\n%s" - lsp-docker--srv-cfg-type-key (ht-keys lsp-clients)))) + base-server-id lsp-docker--srv-cfg-server-key (ht-keys lsp-clients)))) (defun lsp-docker-get-path-mappings (config project-directory) "Get the server path mappings from the top project hash-table CONFIG" @@ -673,8 +665,7 @@ configuration file resides into. The PATH-MAPPINGS provides a hash-table to translate the paths between the host and the dockerized server." (let* ((server-type-subtype (lsp-docker-get-server-type-subtype server-config)) - (config-specified-server-container-name (lsp-docker-get-server-container-name server-config)) - (server-image-name (lsp-docker-get-server-image-name server-config)) + (server-container/image-name (lsp-docker-get-server-container/image-name server-config)) (regular-server-id (lsp-docker-get-server-id server-config)) (server-id (lsp-docker-generate-docker-server-id server-config (lsp-workspace-root))) (server-launch-command (lsp-docker-get-launch-command server-config)) @@ -683,7 +674,7 @@ dockerized server." (lsp-workspace-root) base-client)) (server-container-name (lsp-docker--finalize-docker-server-container-name - config-specified-server-container-name server-config project-root))) + server-container/image-name server-config project-root))) (if (and (lsp-docker-check-server-type-subtype lsp-docker-supported-server-types-subtypes server-type-subtype) @@ -692,19 +683,19 @@ dockerized server." (container-subtype (cdr server-type-subtype))) (pcase container-type ('docker (pcase container-subtype - ('image (if (lsp-docker--check-image-exists server-image-name) + ('image (if (lsp-docker--check-image-exists server-container/image-name) (lsp-docker-register-client-with-activation-fn :server-id regular-server-id :docker-server-id server-id :path-mappings path-mappings - :docker-image-id server-image-name + :docker-image-id server-container/image-name :docker-container-name server-container-name :activation-fn activation-fn :priority lsp-docker-default-priority :server-command server-launch-command :launch-server-cmd-fn #'lsp-docker-launch-new-container) (lsp-docker--build-image-and-register-server-async - :image-name server-image-name + :image-name server-container/image-name :dockerfile-path (lsp-docker--find-project-dockerfile-from-lsp) :server-id regular-server-id :docker-server-id server-id From a0c962134fd9148d6640da9d258a42a8faa02a64 Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Sat, 9 Dec 2023 18:05:46 +0100 Subject: [PATCH 09/11] feat: complete YAML keys replacement with defconst --- lsp-docker.el | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index 7d62d93..490c8ea 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -49,9 +49,18 @@ :group 'lsp-docker :type 'string) +;; top node keys +(defconst lsp-docker--lsp-key 'lsp + "Main key associated to the root-node of the containerized language servers") + +;; 1st sub-node keys (defconst lsp-docker--server-key 'server "LSP sub-key holding a single (or a group of) server(s)") +(defconst lsp-docker--mappings-key 'mappings + "Collection of mappings between host-paths and +containerized-paths (host paths must be within the project)") +;; 2nd sub-node keys ;; supported keys in YAML configuration file(s) (defconst lsp-docker--srv-cfg-type-key 'type "The type of server (at the moment only `docker' is supported).") @@ -282,10 +291,10 @@ be bigger than default servers in order to override them)") (lsp-docker--srv-cfg-subtype-key "image") (lsp-docker--srv-cfg-name-key "emacslsp/lsp-docker-langservers") (lsp-docker--srv-cfg-server-key nil) - ('launch_command nil))) - ('mappings (vector - (ht ('source ".") - ('destination "/projects"))))) + (lsp-docker--srv-cfg-launch-command-key nil))) + (lsp-docker--mappings-key (vector + (ht ('source ".") + ('destination "/projects"))))) "Default configuration for all language servers with persistent configurations" :type 'hash-table :group 'lsp-docker) @@ -294,7 +303,7 @@ be bigger than default servers in order to override them)") "Get the LSP configuration based on a project configuration file" (if (f-exists? project-config-file-path) (if-let* ((whole-config (yaml-parse-string (f-read project-config-file-path))) - (lsp-config (gethash 'lsp whole-config))) + (lsp-config (gethash lsp-docker--lsp-key whole-config))) (cond ;; DO NOT merge to the persistent configuration when a multi-server one is detected ((gethash lsp-docker--multi-server-yml-key lsp-config) @@ -373,7 +382,7 @@ Make sure the '%s' sub-key is set to one of the lsp registered clients:\n\n%s" (defun lsp-docker-get-path-mappings (config project-directory) "Get the server path mappings from the top project hash-table CONFIG" - (if-let ((lsp-mappings-info (gethash 'mappings config))) + (if-let ((lsp-mappings-info (gethash lsp-docker--mappings-key config))) (--map (cons (f-canonical (f-expand (gethash 'source it) project-directory)) (gethash 'destination it)) @@ -382,7 +391,7 @@ Make sure the '%s' sub-key is set to one of the lsp registered clients:\n\n%s" (defun lsp-docker-get-launch-command (server-config) "Get the server launch command from the SERVER-CONFIG hash-table" - (gethash 'launch_command server-config)) + (gethash lsp-docker--srv-cfg-launch-command-key server-config)) (defun lsp-docker-check-server-type-subtype (supported-server-types-subtypes server-type-subtype) "Verify that the combination of server (type . subtype) is supported by the current implementation" From 8461f24ec0e131da24197922ac626831faf2676e Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Sat, 9 Dec 2023 20:51:48 +0100 Subject: [PATCH 10/11] feat: prepare to server list concept --- lsp-docker.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lsp-docker.el b/lsp-docker.el index 490c8ea..d3801c3 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -754,11 +754,10 @@ dockerized server." (multi-server-config (message "registering multiple servers") (--map (lsp-docker--register-single-server - ;; get per-dockerized-server configuration - (gethash it multi-server-config) + it project-root path-mappings) - (ht-keys multi-server-config))) + multi-server-config)) (t (user-error "no '%s' neither '%s' keywords found in configuration file" lsp-docker--server-key From e6dd277e79f7c59d120f0c853eabb667ebf18451 Mon Sep 17 00:00:00 2001 From: Samuele Favazza Date: Sat, 9 Dec 2023 21:51:29 +0100 Subject: [PATCH 11/11] feat: remove 'multi-server' key, implement list of server config instead (update doc) --- README.org | 25 +++++++++++++++++------ lsp-docker.el | 56 +++++++++++++++++++-------------------------------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/README.org b/README.org index c0ce930..dc759a4 100644 --- a/README.org +++ b/README.org @@ -119,11 +119,11 @@ #+begin_src bash EMACS_D_VOLUME=/path/to/spacemacs bash start-spacemacs.sh #+end_src - -** Custom language server containers + +** Custom language server containers You can use manually built language containers or images hosting language server(s), just follow a few simple rules (shown below). The docker images may feature an optional tag, if omitted _latest_ will be assumed. - + *** Building a container (or an image) manually: You have 2 constraints: - A language server must be launched in =stdio= mode (other types of communication are yet to be supported) @@ -147,6 +147,7 @@ It is structured in the following way: #+begin_src yaml + # signle server configuration lsp: server: type: docker @@ -156,7 +157,7 @@ It is structured in the following way: # (see Automatic image building). An image might feature an optional tag, i.e. ':'. If a # tagless image is indicated 'latest' will be assumed. subtype: container - # image/container name to use for this language server + # Image/container name to use for this language server. name: image-container-name # server id of a registered LSP server. You can find the list of registered servers evaluating: # @@ -173,14 +174,26 @@ It is structured in the following way: # NOTE: the paths must be within the project this server is being build for - source: "/your/host/source/path" destination: "/your/path/inside/the/container" + + # multiple server configuration + lsp: + server: + - type: ... + subtype: ... + ... # keys as in the classic single server case, e.g. type, subtype, etc... + - ... # other single server configuration(s) + mappings: + - source: + destination: + ... # other mappings #+end_src *** Registering a language server using a =.dir-locals= file: Just refer to the source code and general conventions of using =.dir-locals=. The variable you need is =lsp-docker-persistent-default-config=, its content is merged with the =lsp= section from a configuration file (if present). - + *** Automatic image building: You can also build an image automatically (currently supported only for =image= subtype): just drop the corresponding =Dockerfile= into the =.lsp-docker= folder in the project root (=Dockerfile= may be named as =Dockerfile= or =Dockerfile.lsp=). Building process is triggered by the =lsp-docker-register= call (you will be prompted whether you want to build the image). Image building *takes place in the project root* (*not* in the =.lsp-docker= subfolder)! In case of an automatic build the image will be registered automatically (based on the values from the config or =.dir-locals= file). - + You can also troubleshoot any issues with supplemental docker calls (checking whether the required image already exists, building a new image) using the supplemental logging functionality: there are 2 variables: first you have to set =lsp-docker-log-docker-supplemental-calls= to true-like value (by default it is =nil=) and then specify the log buffer in the =lsp-docker-log-docker-supplemental-calls-buffer-name= variable (by default it is set to =*lsp-docker-supplemental-calls*=) ** Docker over TRAMP (TBD) diff --git a/lsp-docker.el b/lsp-docker.el index d3801c3..56bc02f 100644 --- a/lsp-docker.el +++ b/lsp-docker.el @@ -81,9 +81,6 @@ registered servers evaluating: `(ht-keys lsp-clients)'.") not used when the `lsp-docker--srv-cfg-subtype-key' is set to container, as the server command shall be the entrypoint.") -(defconst lsp-docker--multi-server-yml-key 'multi-server - "multi-server YAML key, if matched it is assumed multiple language servers are being configured") - (defun lsp-docker--log-docker-supplemental-calls-p () "Return non-nil if should log docker invocation commands" lsp-docker-log-docker-supplemental-calls) @@ -304,12 +301,10 @@ be bigger than default servers in order to override them)") (if (f-exists? project-config-file-path) (if-let* ((whole-config (yaml-parse-string (f-read project-config-file-path))) (lsp-config (gethash lsp-docker--lsp-key whole-config))) - (cond - ;; DO NOT merge to the persistent configuration when a multi-server one is detected - ((gethash lsp-docker--multi-server-yml-key lsp-config) - lsp-config) - (t ; use default values for missing fields in the provided configuration - (ht-merge (ht-copy lsp-docker-persistent-default-config) lsp-config)))))) + ;; use default values for missing fields in the provided configuration + (if (vectorp (gethash lsp-docker--server-key lsp-config)) + lsp-config ; DO NOT merge to the persistent configuration when a multi-server one is detected + (ht-merge (ht-copy lsp-docker-persistent-default-config) lsp-config))))) (defun lsp-docker--find-project-config-file-from-lsp () "Get the LSP configuration file path (project-local configuration, using lsp-mode)" @@ -367,9 +362,7 @@ be bigger than default servers in order to override them)") (defun lsp-docker-get-server-id (server-config) "Get the server id from the SERVER-CONFIG hash-table" - (if (stringp (gethash lsp-docker--srv-cfg-server-key server-config)) - (intern (gethash lsp-docker--srv-cfg-server-key server-config)) - (gethash lsp-docker--srv-cfg-server-key server-config))) + (gethash lsp-docker--srv-cfg-server-key server-config)) (defun lsp-docker--get-base-client (base-server-id) "Get the base lsp client associated to BASE-SERVER-ID key for @@ -732,36 +725,29 @@ dockerized server." "Register one or more dockerized language servers for the current project" (interactive) (if (lsp-workspace-root) - (let* ( - (config (lsp-docker-get-config-from-lsp)) + (let* ((config (lsp-docker-get-config-from-lsp)) (project-root (lsp-workspace-root)) (path-mappings (lsp-docker-get-path-mappings config (lsp-workspace-root))) - (single-server-config (gethash lsp-docker--server-key config)) - (multi-server-config (gethash lsp-docker--multi-server-yml-key config))) + (server-config (gethash lsp-docker--server-key config))) ;; check whether a single or multiple servers are described in the configuration (cond - (single-server-config - (if (and single-server-config multi-server-config) - (display-warning "lsp-docker" - (concat "both single/multiple server configuration detected, " - "ignoring multi-server configuration") - :warning - "*Warnings lsp-docker*")) - (message "registering a single server") - (lsp-docker--register-single-server single-server-config project-root path-mappings)) - - (multi-server-config + ((vectorp server-config) (message "registering multiple servers") - (--map (lsp-docker--register-single-server - it - project-root - path-mappings) - multi-server-config)) + ;; NOTE: if multiple language server descriptions share the same name "server" field, the latest entry + ;; will be enforced. + (--map (lsp-docker--register-single-server it + project-root + path-mappings) + server-config)) + (server-config + (message "registering a single server") + (lsp-docker--register-single-server server-config + project-root + path-mappings)) (t - (user-error "no '%s' neither '%s' keywords found in configuration file" - lsp-docker--server-key - lsp-docker--multi-server-yml-key)))) + (user-error "no `%s' node found in configuration, see README for reference" + lsp-docker--server-key)))) (user-error (format (concat "Current file: %s is not in a registered project! " "Try adding your project with `lsp-workspace-folders-add'")