lsp-php.el (18379B)
1 ;;; lsp-php.el --- description -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2020 emacs-lsp maintainers 4 5 ;; Author: emacs-lsp maintainers 6 ;; Keywords: lsp, php 7 8 ;; This program is free software; you can redistribute it and/or modify 9 ;; it under the terms of the GNU General Public License as published by 10 ;; the Free Software Foundation, either version 3 of the License, or 11 ;; (at your option) any later version. 12 13 ;; This program is distributed in the hope that it will be useful, 14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ;; GNU General Public License for more details. 17 18 ;; You should have received a copy of the GNU General Public License 19 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 20 21 ;;; Commentary: 22 23 ;; LSP Clients for the PHP Programming Language. 24 25 ;;; Code: 26 27 (require 'lsp-mode) 28 (require 'lsp-protocol) 29 30 ;; PHP Language Server 31 (defgroup lsp-php nil 32 "LSP support for PHP, using php-language-server." 33 :link '(url-link "https://github.com/felixfbecker/php-language-server") 34 :group 'lsp-mode) 35 36 (defun lsp-php-get-composer-dir () 37 "Get composer home directory if possible." 38 (if (executable-find "composer") 39 (replace-regexp-in-string "\n$" "" (shell-command-to-string "composer config --global home")) 40 "~/.composer")) 41 42 (defcustom lsp-php-composer-dir nil 43 "Home directory of composer." 44 :group 'lsp-php 45 :type 'string) 46 47 (defcustom lsp-clients-php-server-command nil 48 "Install directory for php-language-server." 49 :group 'lsp-php 50 :type '(repeat string)) 51 52 (defun lsp-php--create-connection () 53 "Create lsp connection." 54 (lsp-stdio-connection 55 (lambda () 56 (unless lsp-php-composer-dir 57 (setq lsp-php-composer-dir (lsp-php-get-composer-dir))) 58 (unless lsp-clients-php-server-command 59 (setq lsp-clients-php-server-command 60 `("php", 61 (expand-file-name 62 (f-join lsp-php-composer-dir "vendor/felixfbecker/language-server/bin/php-language-server.php"))))) 63 lsp-clients-php-server-command) 64 (lambda () 65 (if (and (cdr lsp-clients-php-server-command) 66 (eq (string-match-p "php[0-9.]*\\'" (car lsp-clients-php-server-command)) 0)) 67 ;; Start with the php command and the list has more elems. Test the existence of the PHP script. 68 (let ((php-file (nth 1 lsp-clients-php-server-command))) 69 (or (file-exists-p php-file) 70 (progn 71 (lsp-log "%s is not present." php-file) 72 nil))) 73 t)))) 74 75 (lsp-register-client 76 (make-lsp-client :new-connection (lsp-php--create-connection) 77 :activation-fn (lsp-activate-on "php") 78 :priority -3 79 :server-id 'php-ls)) 80 81 ;;; Intelephense 82 (defgroup lsp-intelephense nil 83 "LSP support for PHP, using Intelephense." 84 :group 'lsp-mode 85 :link '(url-link "https://github.com/bmewburn/vscode-intelephense") 86 :package-version '(lsp-mode . "6.1")) 87 88 (lsp-defcustom lsp-intelephense-php-version "9.0.0" 89 "Minimum version of PHP to refer to. Affects code actions, diagnostic & 90 completions." 91 :type 'string 92 :group 'lsp-intelephense 93 :package-version '(lsp-mode . "6.1") 94 :lsp-path "intelephense.environment.phpVersion") 95 96 (lsp-defcustom lsp-intelephense-files-max-size 1000000 97 "Maximum file size in bytes." 98 :type 'number 99 :group 'lsp-intelephense 100 :package-version '(lsp-mode . "6.1") 101 :lsp-path "intelephense-files.maxSize") 102 103 (lsp-defcustom lsp-intelephense-files-associations 104 ["*.php" "*.phtml"] 105 "Configure glob patterns to make files available for language 106 server features." 107 :type '(repeat string) 108 :group 'lsp-intelephense 109 :package-version '(lsp-mode . "6.1") 110 :lsp-path "intelephense.files.associations") 111 112 (lsp-defcustom lsp-intelephense-files-exclude 113 ["**/.git/**" "**/.svn/**" "**/.hg/**" "**/CVS/**" "**/.DS_Store/**" 114 "**/node_modules/**" "**/bower_components/**" "**/vendor/**/{Test,test,Tests,tests}/**"] 115 "Configure glob patterns to exclude certain files and folders 116 from all language server features." 117 :type '(repeat string) 118 :group 'lsp-intelephense 119 :package-version '(lsp-mode . "6.1") 120 :lsp-path "intelephense.files.exclude") 121 122 (lsp-defcustom lsp-intelephense-paths-include 123 [] 124 "Configure additional paths outside workspace." 125 :type 'lsp-string-vector 126 :group 'lsp-intelephense 127 :package-version '(lsp-mode . "8.1") 128 :lsp-path "intelephense.environment.includePaths") 129 130 (lsp-defcustom lsp-intelephense-stubs 131 ["apache" "bcmath" "bz2" "calendar" 132 "com_dotnet" "Core" "ctype" "curl" "date" "dba" "dom" "enchant" 133 "exif" "fileinfo" "filter" "fpm" "ftp" "gd" "hash" "iconv" "imap" "interbase" 134 "intl" "json" "ldap" "libxml" "mbstring" "mcrypt" "meta" "mssql" "mysqli" 135 "oci8" "odbc" "openssl" "pcntl" "pcre" "PDO" "pdo_ibm" "pdo_mysql" 136 "pdo_pgsql" "pdo_sqlite" "pgsql" "Phar" "posix" "pspell" "readline" "recode" 137 "Reflection" "regex" "session" "shmop" "SimpleXML" "snmp" "soap" "sockets" 138 "sodium" "SPL" "sqlite3" "standard" "superglobals" "sybase" "sysvmsg" 139 "sysvsem" "sysvshm" "tidy" "tokenizer" "wddx" "xml" "xmlreader" "xmlrpc" 140 "xmlwriter" "Zend OPcache" "zip" "zlib"] 141 "Configure stub files for built in symbols and common 142 extensions. The default setting includes PHP core and all 143 bundled extensions." 144 :type '(repeat string) 145 :group 'lsp-intelephense 146 :package-version '(lsp-mode . "6.1") 147 :lsp-path "intelephense.stubs") 148 149 (lsp-defcustom lsp-intelephense-completion-insert-use-declaration t 150 "Use declarations will be automatically inserted for namespaced 151 classes, traits, interfaces, functions, and constants." 152 :type 'boolean 153 :group 'lsp-intelephense 154 :package-version '(lsp-mode . "6.1") 155 :lsp-path "intelephense.completion.insertUseDeclaration") 156 157 (lsp-defcustom lsp-intelephense-completion-fully-qualify-global-constants-and-functions nil 158 "Global namespace constants and functions will be fully 159 qualified (prefixed with a backslash)." 160 :type 'boolean 161 :group 'lsp-intelephense 162 :package-version '(lsp-mode . "6.1") 163 :lsp-path "intelephense.completion.fullyQualifyGlobalConstantsAndFunctions") 164 165 (lsp-defcustom lsp-intelephense-completion-trigger-parameter-hints t 166 "Method and function completions will include parentheses and 167 trigger parameter hints." 168 :type 'boolean 169 :group 'lsp-intelephense 170 :package-version '(lsp-mode . "6.2") 171 :lsp-path "intelephense.completion.triggerParameterHints") 172 173 (lsp-defcustom lsp-intelephense-completion-max-items 100 174 "The maximum number of completion items returned per request." 175 :type 'number 176 :group 'lsp-intelephense 177 :package-version '(lsp-mode . "6.2") 178 :lsp-path "intelephense.completion.maxItems") 179 180 (lsp-defcustom lsp-intelephense-format-enable t 181 "Enables formatting." 182 :type 'boolean 183 :group 'lsp-intelephense 184 :package-version '(lsp-mode . "6.1") 185 :lsp-path "intelephense.format.enable") 186 187 (lsp-defcustom lsp-intelephense-format-braces "psr12" 188 "Formatting braces style. psr12, allman or k&r" 189 :type 'string 190 :group 'lsp-intelephense 191 :package-version '(lsp-mode . "8.1") 192 :lsp-path "intelephense.format.braces") 193 194 (defcustom lsp-intelephense-licence-key nil 195 "Enter your intelephense licence key here to access premium 196 features." 197 :type 'string 198 :group 'lsp-intelephense 199 :package-version '(lsp-mode . "6.2")) 200 201 (lsp-defcustom lsp-intelephense-telemetry-enabled nil 202 "Anonymous usage and crash data will be sent to Azure 203 Application Insights." 204 :type 'boolean 205 :group 'lsp-intelephense 206 :package-version '(lsp-mode . "6.2") 207 :lsp-path "intelephense.telemetry.enabled") 208 209 (lsp-defcustom lsp-intelephense-rename-exclude 210 ["**/vendor/**"] 211 "Glob patterns to exclude files and folders from having symbols 212 renamed. Rename operation will fail if references and/or 213 definitions are found in excluded files/folders." 214 :type '(repeat string) 215 :group 'lsp-intelephense 216 :package-version '(lsp-mode . "6.2") 217 :lsp-path "intelephense.rename.exclude") 218 219 (lsp-defcustom lsp-intelephense-trace-server "off" 220 "Traces the communication between VSCode and the intelephense 221 language server." 222 :type '(choice (:tag "off" "messages" "verbose")) 223 :group 'lsp-intelephense 224 :package-version '(lsp-mode . "6.1") 225 :lsp-path "intelephense.trace.server") 226 227 (defcustom lsp-intelephense-storage-path 228 (expand-file-name (locate-user-emacs-file "lsp-cache")) 229 "Optional absolute path to storage dir." 230 :type 'directory 231 :group 'lsp-intelephense 232 :package-version '(lsp-mode . "6.1")) 233 234 (defcustom lsp-intelephense-global-storage-path 235 (expand-file-name (locate-user-emacs-file "intelephense")) 236 "Optional absolute path to global storage dir." 237 :type 'directory 238 :group 'lsp-intelephense 239 :package-version '(lsp-mode . "9.0.0")) 240 241 (defcustom lsp-intelephense-clear-cache nil 242 "Optional flag to clear server state." 243 :type 'boolean 244 :group 'lsp-intelephense 245 :package-version '(lsp-mode . "6.2")) 246 247 (defcustom lsp-intelephense-multi-root t 248 "Flag to control if the server supports multi-root projects." 249 :type 'boolean 250 :group 'lsp-intelephense 251 :package-version '(lsp-mode . "6.3")) 252 253 (define-obsolete-variable-alias 254 'lsp-clients-php-iph-server-command 255 'lsp-intelephense-server-command 256 "lsp-mode 6.1") 257 258 (defcustom lsp-intelephense-server-command 259 `("intelephense" "--stdio") 260 "Command to start Intelephense." 261 :type '(repeat string) 262 :group 'lsp-intelephense 263 :package-version '(lsp-mode . "6.1")) 264 265 (lsp-dependency 'intelephense 266 '(:system "intelephense") 267 '(:npm :package "intelephense" 268 :path "intelephense")) 269 270 (lsp-register-client 271 (make-lsp-client :new-connection (lsp-stdio-connection 272 (lambda () 273 `(,(or (executable-find 274 (cl-first lsp-intelephense-server-command)) 275 (lsp-package-path 'intelephense)) 276 ,@(cl-rest lsp-intelephense-server-command)))) 277 :activation-fn (lsp-activate-on "php") 278 :priority -1 279 :notification-handlers (ht ("indexingStarted" #'ignore) 280 ("indexingEnded" #'ignore)) 281 :initialization-options (lambda () 282 (list :storagePath lsp-intelephense-storage-path 283 :globalStoragePath lsp-intelephense-global-storage-path 284 :licenceKey lsp-intelephense-licence-key 285 :clearCache lsp-intelephense-clear-cache)) 286 :multi-root lsp-intelephense-multi-root 287 :completion-in-comments? t 288 :server-id 'iph 289 :download-server-fn (lambda (_client callback error-callback _update?) 290 (lsp-package-ensure 'intelephense 291 callback error-callback)) 292 :synchronize-sections '("intelephense"))) 293 294 295 ;;; Serenata 296 (defgroup lsp-serenata nil 297 "LSP support for the PHP programming language, using serenata." 298 :group 'lsp-mode 299 :link '(url-link "https://gitlab.com/Serenata/Serenata") 300 :package-version '(lsp-mode . "7.0")) 301 302 (defcustom lsp-serenata-server-path 303 "serenata.phar" 304 "Path to the Serenata Language Server phar file. 305 It can be downloaded from https://gitlab.com/Serenata/Serenata/-/releases." 306 :group 'lsp-serenata 307 :type 'file) 308 309 (defcustom lsp-serenata-uris 310 [] 311 "A list of folders to index for your project. 312 This does not have to include the root of the project itself, in 313 case you have need of an exotic configuration where the root of 314 the project is at some location but your actual PHP code is 315 somewhere else. Note that if you are running Serenata in a 316 container, you will have to ensure that these URI's are mapped 317 inside it. Avoid using file paths containing spaces. This is 318 currently broken due to apparent PHP quirks. By default, the 319 value is taken from the lsp workspace location." 320 :group 'lsp-serenata 321 :type 'lsp-string-vector) 322 323 (defcustom lsp-serenata-php-version 324 7.3 325 "Allows you to specify the PHP version your project is written in. 326 At the moment this directive is still ignored, but it will 327 influence functionality such as refactoring in the future, where 328 older PHP versions may not support scalar type hints, which may 329 then be omitted from places such as getters and setters." 330 :group 'lsp-serenata 331 :type 'number) 332 333 (defcustom lsp-serenata-file-extensions 334 ["php"] 335 "List of file extensions (without dot) to process. 336 Files that do not match this whitelist will be ignored during 337 indexing. Usually you'll want to set this to at least include 338 php, as it is the most common PHP extension. phpt is not 339 included by default as it is often used to contain test code that 340 is not directly part of the code. Note that for existing 341 projects, removing extensions will not not automatically prune 342 files having them from the index if they are already present. 343 Adding new ones will cause the files having them to be picked up 344 on the next project initialization." 345 :group 'lsp-serenata 346 :type 'lsp-string-vector) 347 348 (defcustom lsp-serenata-index-database-uri (lsp--path-to-uri (f-join user-emacs-directory "index.sqlite")) 349 "The location to store the index database. 350 Note that, as the index database uses SQLite and WAL mode, 351 additional files (usually two) may be generated and used in the 352 same folder. Note also that Serenata relies on the Doctrine DBAL 353 library as well as the SQLite backends in PHP, which may not 354 support non-file URI's, which may prevent you from using these." 355 :group 'lsp-serenata 356 :type 'file) 357 358 (defcustom lsp-serenata-exclude-path-expressions ["/.+Test.php$/"] 359 "One or more expressions of paths to ignore. 360 This uses Symfony's Finder in the background, so this means you 361 can configure anything here that can also be passed to the name 362 function, which includes plain strings, globs, as well as regular 363 expressions. Note that for existing projects, modifying these 364 will not not automatically prune them from the index if they are 365 already present." 366 :group 'lsp-serenata 367 :type 'lsp-string-vector) 368 369 (defun lsp-serenata-server-start-fun (port) 370 "Define serenata start function, it requires a PORT." 371 `(,lsp-serenata-server-path 372 "-u" ,(number-to-string port))) 373 374 (defun lsp-serenata-init-options () 375 "Init options for lsp-serenata." 376 `( :configuration ( :uris ,lsp-serenata-uris 377 :indexDatabaseUri ,lsp-serenata-index-database-uri 378 :phpVersion ,lsp-serenata-php-version 379 :excludedPathExpressions ,lsp-serenata-exclude-path-expressions 380 :fileExtensions ,lsp-serenata-file-extensions))) 381 382 383 (lsp-interface (serenata:didProgressIndexing (:sequenceOfIndexedItem :totalItemsToIndex :progressPercentage :folderUri :fileUri :info) nil )) 384 385 (lsp-register-client 386 (make-lsp-client 387 :new-connection (lsp-tcp-connection 'lsp-serenata-server-start-fun) 388 :activation-fn (lsp-activate-on "php") 389 :priority -2 390 :notification-handlers (ht ("serenata/didProgressIndexing" 391 (lambda (_server data) 392 (lsp-log "%s" (lsp:serenata-did-progress-indexing-info data))))) 393 394 :initialization-options #'lsp-serenata-init-options 395 :initialized-fn (lambda (workspace) 396 (when (equal (length lsp-serenata-uris) 0) 397 (let* ((lsp-root (lsp--path-to-uri (lsp-workspace-root)))) 398 (setq lsp-serenata-uris (vector lsp-root)))) 399 (with-lsp-workspace workspace 400 (lsp--set-configuration 401 (lsp-configuration-section "serenata")))) 402 :server-id 'serenata)) 403 404 ;;; phpactor 405 406 (defgroup lsp-phpactor nil 407 "LSP support for Phpactor." 408 :link '(url-link "https://github.com/phpactor/phpactor") 409 :group 'lsp-mode) 410 411 (defcustom lsp-phpactor-path nil 412 "Path to the `phpactor' command." 413 :group 'lsp-phpactor 414 :type 'string) 415 416 (lsp-register-client 417 (make-lsp-client 418 :new-connection (lsp-stdio-connection 419 (lambda () 420 (unless lsp-php-composer-dir 421 (setq lsp-php-composer-dir (lsp-php-get-composer-dir))) 422 (unless lsp-phpactor-path 423 (setq lsp-phpactor-path (or (executable-find "phpactor") 424 (f-join lsp-php-composer-dir "vendor/phpactor/phpactor/bin/phpactor")))) 425 (list lsp-phpactor-path "language-server"))) 426 :activation-fn (lsp-activate-on "php") 427 ;; `phpactor' is not really that feature-complete: it doesn't support 428 ;; `textDocument/showOccurence' and sometimes errors (e.g. find references on 429 ;; a global free-standing function). 430 :priority -4 431 ;; Even though `phpactor' itself supports no options, this needs to be 432 ;; serialized as an empty object (otherwise the LS won't even start, due to a 433 ;; type error). 434 :initialization-options (ht) 435 :server-id 'phpactor)) 436 437 (defcustom lsp-phpactor-extension-alist '(("Phpstan" . "phpactor/language-server-phpstan-extension") 438 ("Behat" . "phpactor/behat-extension") 439 ("PHPUnit" . "phpactor/phpunit-extension")) 440 "Alist mapping extension names to `composer' packages. 441 These extensions can be installed using 442 `lsp-phpactor-install-extension'." 443 :type '(alist :key-type "string" :value-type "string") 444 :group 'lsp-phpactor) 445 446 (defun lsp-phpactor-install-extension (extension) 447 "Install a `phpactor' EXTENSION. 448 See `lsp-phpactor-extension-alist' and 449 https://phpactor.readthedocs.io/en/develop/extensions.html." 450 (interactive (list (completing-read "Select extension: " 451 lsp-phpactor-extension-alist))) 452 (compilation-start 453 (format "%s extension:install %s" 454 (shell-quote-argument (expand-file-name lsp-phpactor-path)) 455 (shell-quote-argument 456 (cdr (assoc extension lsp-phpactor-extension-alist)))) 457 nil 458 (lambda (_mode) 459 (format "*Phpactor install %s*" extension)))) 460 461 (lsp-consistency-check lsp-php) 462 463 (provide 'lsp-php) 464 ;;; lsp-php.el ends here