|
|
- ;;; ycmd.el --- emacs bindings to the ycmd completion server -*- lexical-binding: t -*-
- ;;
- ;; Copyright (c) 2014-2017 Austin Bingham, Peter Vasil
- ;;
- ;; Authors: Austin Bingham <austin.bingham@gmail.com>
- ;; Peter Vasil <mail@petervasil.net>
- ;; Version: 1.3-cvs
- ;; URL: https://github.com/abingham/emacs-ycmd
- ;; Package-Requires: ((emacs "24.4") (dash "2.13.0") (s "1.11.0") (deferred "0.5.1") (cl-lib "0.6.1") (let-alist "1.0.5") (request "0.3.0") (request-deferred "0.3.0") (pkg-info "0.6"))
- ;;
- ;; This file is not part of GNU Emacs.
- ;;
- ;;; Commentary:
- ;;
- ;; Description:
- ;;
- ;; ycmd is a modular server that provides completion for C/C++/ObjC
- ;; and Python, among other languages. This module provides an emacs
- ;; client for that server.
- ;;
- ;; ycmd is a bit peculiar in a few ways. First, communication with the
- ;; server uses HMAC to authenticate HTTP messages. The server is
- ;; started with an HMAC secret that the client uses to generate hashes
- ;; of the content it sends. Second, the server gets this HMAC
- ;; information (as well as other configuration information) from a
- ;; file that the server deletes after reading. So when the code in
- ;; this module starts a server, it has to create a file containing the
- ;; secret code. Since the server deletes this file, this code has to
- ;; create a new one for each server it starts. Hopefully by knowing
- ;; this, you'll be able to make more sense of some of what you see
- ;; below.
- ;;
- ;; For more details, see the project page at
- ;; https://github.com/abingham/emacs-ycmd.
- ;;
- ;; Installation:
- ;;
- ;; Copy this file to to some location in your emacs load path. Then add
- ;; "(require 'ycmd)" to your emacs initialization (.emacs,
- ;; init.el, or something).
- ;;
- ;; Example config:
- ;;
- ;; (require 'ycmd)
- ;; (ycmd-setup)
- ;;
- ;; Basic usage:
- ;;
- ;; First you'll want to configure a few things. If you've got a global
- ;; ycmd config file, you can specify that with `ycmd-global-config':
- ;;
- ;; (set-variable 'ycmd-global-config "/path/to/global_conf.py")
- ;;
- ;; Then you'll want to configure your "extra-config whitelist"
- ;; patterns. These patterns determine which extra-conf files will get
- ;; loaded automatically by ycmd. So, for example, if you want to make
- ;; sure that ycmd will automatically load all of the extra-conf files
- ;; underneath your "~/projects" directory, do this:
- ;;
- ;; (set-variable 'ycmd-extra-conf-whitelist '("~/projects/*"))
- ;;
- ;; Now, the first time you open a file for which ycmd can perform
- ;; completions, a ycmd server will be automatically started.
- ;;
- ;; When ycmd encounters an extra-config that's not on the white list,
- ;; it checks `ycmd-extra-conf-handler' to determine what to do. By
- ;; default this is set to `ask', in which case the user is asked
- ;; whether to load the file or ignore it. You can also set it to
- ;; `load', in which case all extra-confs are loaded (and you don't
- ;; really need to worry about `ycmd-extra-conf-whitelist'.) Or you can
- ;; set this to `ignore', in which case all extra-confs are
- ;; automatically ignored.
- ;;
- ;; Use `ycmd-get-completions' to get completions at some point in a
- ;; file. For example:
- ;;
- ;; (ycmd-get-completions buffer position)
- ;;
- ;; You can use `ycmd-display-completions' to toy around with completion
- ;; interactively and see the shape of the structures in use.
- ;;
- ;;; License:
- ;;
- ;; Permission is hereby granted, free of charge, to any person
- ;; obtaining a copy of this software and associated documentation
- ;; files (the "Software"), to deal in the Software without
- ;; restriction, including without limitation the rights to use, copy,
- ;; modify, merge, publish, distribute, sublicense, and/or sell copies
- ;; of the Software, and to permit persons to whom the Software is
- ;; furnished to do so, subject to the following conditions:
- ;;
- ;; The above copyright notice and this permission notice shall be
- ;; included in all copies or substantial portions of the Software.
- ;;
- ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- ;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- ;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- ;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- ;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- ;; SOFTWARE.
-
- ;;; Code:
-
- (eval-when-compile
- (require 'cl-lib)
- (require 'let-alist))
- (require 'dash)
- (require 's)
- (require 'deferred)
- (require 'hmac-def)
- (require 'json)
- (require 'request)
- (require 'request-deferred)
- (require 'etags)
- (require 'easymenu)
- (require 'diff)
- (require 'diff-mode)
-
- (declare-function pkg-info-version-info "pkg-info" (package))
-
- (defgroup ycmd nil
- "a ycmd emacs client"
- :link '(url-link :tag "Github" "https://github.com/abingham/emacs-ycmd")
- :group 'tools
- :group 'programming)
-
- (defcustom ycmd-global-config nil
- "Path to global extra conf file."
- :type '(string))
-
- (defcustom ycmd-extra-conf-whitelist nil
- "List of glob expressions which match extra configs.
- Whitelisted configs are loaded without confirmation."
- :type '(repeat string))
-
- (defcustom ycmd-extra-conf-handler 'ask
- "What to do when an un-whitelisted extra config is encountered.
-
- Options are:
-
- `load'
- Automatically load unknown extra confs.
-
- `ignore'
- Ignore unknown extra confs and do not load them.
-
- `ask'
- Ask the user for each unknown extra conf."
- :type '(choice (const :tag "Load unknown extra confs" load)
- (const :tag "Ignore unknown extra confs" ignore)
- (const :tag "Ask the user" ask))
- :risky t)
-
- (defcustom ycmd-host "127.0.0.1"
- "The host on which the ycmd server is running."
- :type '(string))
-
- (defcustom ycmd-server-command nil
- "The ycmd server program command.
-
- The value is a list of arguments to run the ycmd server.
- Example value:
-
- \(set-variable 'ycmd-server-command (\"python\" \"/path/to/ycmd/package/\"))"
- :type '(repeat string))
-
- (defcustom ycmd-server-args '("--log=debug"
- "--keep_logfile"
- "--idle_suicide_seconds=10800")
- "Extra arguments to pass to the ycmd server."
- :type '(repeat string))
-
- (defcustom ycmd-server-port nil
- "The ycmd server port. If nil, use random port."
- :type '(number))
-
- (defcustom ycmd-file-parse-result-hook nil
- "Functions to run with file-parse results.
-
- Each function will be called with with the results returned from
- ycmd when it parses a file in response to
- /event_notification."
- :type 'hook
- :risky t)
-
- (defcustom ycmd-idle-change-delay 0.5
- "Number of seconds to wait after buffer modification before
- re-parsing the contents."
- :type '(number)
- :safe #'numberp)
-
- (defcustom ycmd-keepalive-period 600
- "Number of seconds between keepalive messages."
- :type '(number))
-
- (defcustom ycmd-startup-timeout 3
- "Number of seconds to wait for the server to start."
- :type '(number))
-
- (defcustom ycmd-delete-process-delay 3
- "Seconds to wait for the server to finish before killing the process."
- :type '(number))
-
- (defcustom ycmd-parse-conditions '(save new-line mode-enabled)
- "When ycmd should reparse the buffer.
-
- The variable is a list of events that may trigger parsing the
- buffer for new completion:
-
- `save'
- Set buffer-needs-parse flag after the buffer was saved.
-
- `new-line'
- Set buffer-needs-parse flag immediately after a new
- line was inserted into the buffer.
-
- `idle-change'
- Set buffer-needs-parse flag a short time after a
- buffer has changed. (See `ycmd-idle-change-delay')
-
- `mode-enabled'
- Set buffer-needs-parse flag after `ycmd-mode' has been
- enabled.
-
- `buffer-focus'
- Set buffer-needs-parse flag when an unparsed buffer gets
- focus.
-
- If nil, never set buffer-needs-parse flag. For a manual reparse,
- use `ycmd-parse-buffer'."
- :type '(set (const :tag "After the buffer was saved" save)
- (const :tag "After a new line was inserted" new-line)
- (const :tag "After a buffer was changed and idle" idle-change)
- (const :tag "After a `ycmd-mode' was enabled" mode-enabled)
- (const :tag "After an unparsed buffer gets focus" buffer-focus))
- :safe #'listp)
-
- (defcustom ycmd-default-tags-file-name "tags"
- "The default tags file name."
- :type 'string)
-
- (defcustom ycmd-force-semantic-completion nil
- "Whether to use always semantic completion."
- :type 'boolean)
-
- (defcustom ycmd-auto-trigger-semantic-completion t
- "If non-nil, semantic completion is turned off.
- Semantic completion is still available if
- `ycmd-force-semantic-completion' is non-nil."
- :type 'boolean)
-
- (defcustom ycmd-hide-url-status t
- "Whether to quash url status messages for ycmd requests."
- :type 'boolean)
-
- (defcustom ycmd-bypass-url-proxy-services t
- "Bypass proxies for local traffic with the ycmd server.
-
- If non-nil, bypass the variable `url-proxy-services' in
- `ycmd--request' by setting it to nil and add `no_proxy' to
- `process-environment' to bypass proxies when using `curl' as
- `request-backend' and for the ycmd process."
- :type 'boolean)
-
- (defcustom ycmd-tag-files nil
- "Whether to collect identifiers from tags file.
-
- nil
- Do not collect identifiers from tag files.
-
- `auto'
- Look up directory hierarchy for first found tags file with
- `ycmd-default-tags-file-name'.
-
- string
- A tags file name.
-
- list
- A list of tag file names."
- :type '(choice (const :tag "Don't use tag file." nil)
- (const :tag "Locate tags file automatically" auto)
- (string :tag "Tag file name")
- (repeat :tag "List of tag files"
- (string :tag "Tag file name")))
- :safe (lambda (obj)
- (or (symbolp obj)
- (stringp obj)
- (ycmd--string-list-p obj))))
-
- (defcustom ycmd-file-type-map
- '((c++-mode . ("cpp"))
- (c-mode . ("c"))
- (caml-mode . ("ocaml"))
- (csharp-mode . ("cs"))
- (d-mode . ("d"))
- (erlang-mode . ("erlang"))
- (emacs-lisp-mode . ("elisp"))
- (go-mode . ("go"))
- (java-mode . ("java"))
- (js-mode . ("javascript"))
- (js2-mode . ("javascript"))
- (lua-mode . ("lua"))
- (objc-mode . ("objc"))
- (perl-mode . ("perl"))
- (cperl-mode . ("perl"))
- (php-mode . ("php"))
- (python-mode . ("python"))
- (ruby-mode . ("ruby"))
- (rust-mode . ("rust"))
- (swift-mode . ("swift"))
- (scala-mode . ("scala"))
- (tuareg-mode . ("ocaml"))
- (typescript-mode . ("typescript")))
- "Mapping from major modes to ycmd file-type strings.
-
- Used to determine a) which major modes we support and b) how to
- describe them to ycmd."
- :type '(alist :key-type symbol :value-type (repeat string)))
-
- (defcustom ycmd-min-num-chars-for-completion 2
- "The minimum number of characters for identifier completion.
-
- It controls the number of characters the user needs to type
- before identifier-based completion suggestions are triggered.
-
- This option is NOT used for semantic completion.
-
- Setting this it to a high number like 99 effectively turns off
- the identifier completion engine and just leaves the semantic
- engine."
- :type 'integer)
-
- (defcustom ycmd-max-num-identifier-candidates 10
- "The maximum number of identifier completion results."
- :type 'integer)
-
- (defcustom ycmd-seed-identifiers-with-keywords nil
- "Whether to seed identifier database with keywords."
- :type 'boolean)
-
- (defcustom ycmd-get-keywords-function 'ycmd--get-keywords-from-alist
- "Function to get keywords for current mode."
- :type 'symbol)
-
- (defcustom ycmd-gocode-binary-path nil
- "Gocode binary path."
- :type 'string)
-
- (defcustom ycmd-godef-binary-path nil
- "Godef binary path."
- :type 'string)
-
- (defcustom ycmd-rust-src-path nil
- "Rust source path."
- :type 'string)
-
- (defcustom ycmd-swift-src-path nil
- "Swift source path."
- :type 'string)
-
- (defcustom ycmd-racerd-binary-path nil
- "Racerd binary path."
- :type 'string)
-
- (defcustom ycmd-python-binary-path nil
- "Python binary path."
- :type 'string)
-
- (defcustom ycmd-global-modes t
- "Modes for which `ycmd-mode' is turned on by `global-ycmd-mode'.
-
- If t, ycmd mode is turned on for all major modes in
- `ycmd-file-type-map'. If set to all, ycmd mode is turned on
- for all major-modes. If a list, ycmd mode is turned on for all
- `major-mode' symbols in that list. If the `car' of the list is
- `not', ycmd mode is turned on for all `major-mode' symbols _not_
- in that list. If nil, ycmd mode is never turned on by
- `global-ycmd-mode'."
- :type '(choice (const :tag "none" nil)
- (const :tag "member in `ycmd-file-type-map'" t)
- (const :tag "all" all)
- (set :menu-tag "mode specific" :tag "modes"
- :value (not)
- (const :tag "Except" not)
- (repeat :inline t (symbol :tag "mode")))))
-
- (defcustom ycmd-confirm-fixit t
- "Whether to confirm when applying fixit on line."
- :type 'boolean)
-
- (defcustom ycmd-after-exception-hook nil
- "Function to run if server request resulted in exception.
-
- This hook is run whenever an exception is thrown after a ycmd
- server request. Four arguments are passed to the function, a
- string with the type of request that triggerd the exception, the
- buffer and the point at the time of the request and the server
- response structure which looks like this:
-
- ((exception
- (TYPE . \"RuntimeError\"))
- (traceback . \"long traceback string\")
- (message . \"Can't jump to definition.\"))
-
- This variable is a normal hook. See Info node `(elisp)Hooks'."
- :type 'hook
- :risky t)
-
- (defcustom ycmd-after-teardown-hook nil
- "Functions to run after execution of `ycmd--teardown'.
-
- This variable is a normal hook. See Info node `(elisp)Hooks'."
- :type 'hook
- :risky t)
-
- (defcustom ycmd-mode-line-prefix "ycmd"
- "Base mode line lighter for ycmd."
- :type 'string
- :package-version '(ycmd . "1.3"))
-
- (defcustom ycmd-completing-read-function #'completing-read
- "Function to read from minibuffer with completion.
-
- The function must be compatible to the built-in `completing-read'
- function."
- :type '(choice (const :tag "Default" completing-read)
- (const :tag "IDO" ido-completing-read)
- (function :tag "Custom function"))
- :risky t
- :package-version '(ycmd . "1.2"))
-
- (defconst ycmd--file-types-with-diagnostics
- '("c"
- "cpp"
- "objc"
- "objcpp"
- "cs"
- "typescript")
- "A list of ycmd file type strings which support semantic diagnostics.")
-
- (defvar ycmd-keywords-alist
- '((c++-mode
- "alignas" "alignof" "and" "and_eq" "asm" "auto" "bitand" "bitor" "bool"
- "break" "case" "catch" "char" "char16_t" "char32_t" "class" "compl"
- "concept" "const" "const_cast" "constexpr" "continue" "decltype" "default"
- "define" "defined" "delete" "do" "double" "dynamic_cast" "elif" "else"
- "endif" "enum" "error" "explicit" "export" "extern" "false" "final" "float"
- "for" "friend" "goto" "if" "ifdef" "ifndef" "include" "inline" "int" "line"
- "long" "mutable" "namespace" "new" "noexcept" "not" "not_eq" "nullptr"
- "operator" "or" "or_eq" "override" "pragma" "_Pragma" "private" "protected"
- "public" "register" "reinterpret_cast" "requires" "return" "short" "signed"
- "sizeof" "static" "static_assert" "static_cast" "struct" "switch"
- "template" "this" "thread_local" "throw" "true" "try" "typedef" "typeid"
- "typename" "union" "unsigned" "using" "virtual" "void" "volatile" "wchar_t"
- "while" "xor" "xor_eq")
- (c-mode
- "auto" "_Alignas" "_Alignof" "_Atomic" "_Bool" "break" "case" "char"
- "_Complex" "const" "continue" "default" "define" "defined" "do" "double"
- "elif" "else" "endif" "enum" "error" "extern" "float" "for" "goto"
- "_Generic" "if" "ifdef" "ifndef" "_Imaginary" "include" "inline" "int"
- "line" "long" "_Noreturn" "pragma" "register" "restrict" "return" "short"
- "signed" "sizeof" "static" "struct" "switch" "_Static_assert" "typedef"
- "_Thread_local" "undef" "union" "unsigned" "void" "volatile" "while")
- (go-mode
- "break" "case" "chan" "const" "continue" "default" "defer" "else"
- "fallthrough" "for" "func" "go" "goto" "if" "import" "interface" "map"
- "package" "range" "return" "select" "struct" "switch" "type" "var")
- (lua-mode
- "and" "break" "do" "else" "elseif" "end" "false" "for" "function" "if" "in"
- "local" "nil" "not" "or" "repeat" "return" "then" "true" "until" "while")
- (python-mode
- "ArithmeticError" "AssertionError" "AttributeError" "BaseException"
- "BufferError" "BytesWarning" "DeprecationWarning" "EOFError" "Ellipsis"
- "EnvironmentError" "Exception" "False" "FloatingPointError" "FutureWarning"
- "GeneratorExit" "IOError" "ImportError" "ImportWarning" "IndentationError"
- "IndexError" "KeyError" "KeyboardInterrupt" "LookupError" "MemoryError"
- "NameError" "None" "NotImplemented" "NotImplementedError" "OSError"
- "OverflowError" "PendingDeprecationWarning" "ReferenceError" "RuntimeError"
- "RuntimeWarning" "StandardError" "StopIteration" "SyntaxError"
- "SyntaxWarning" "SystemError" "SystemExit" "TabError" "True" "TypeError"
- "UnboundLocalError" "UnicodeDecodeError" "UnicodeEncodeError"
- "UnicodeError" "UnicodeTranslateError" "UnicodeWarning" "UserWarning"
- "ValueError" "Warning" "ZeroDivisionError" "__builtins__" "__debug__"
- "__doc__" "__file__" "__future__" "__import__" "__init__" "__main__"
- "__name__" "__package__" "_dummy_thread" "_thread" "abc" "abs" "aifc" "all"
- "and" "any" "apply" "argparse" "array" "as" "assert" "ast" "asynchat"
- "asyncio" "asyncore" "atexit" "audioop" "base64" "basestring" "bdb" "bin"
- "binascii" "binhex" "bisect" "bool" "break" "buffer" "builtins" "bytearray"
- "bytes" "bz2" "calendar" "callable" "cgi" "cgitb" "chr" "chuck" "class"
- "classmethod" "cmath" "cmd" "cmp" "code" "codecs" "codeop" "coerce"
- "collections" "colorsys" "compile" "compileall" "complex" "concurrent"
- "configparser" "contextlib" "continue" "copy" "copyreg" "copyright"
- "credits" "crypt" "csv" "ctypes" "curses" "datetime" "dbm" "decimal" "def"
- "del" "delattr" "dict" "difflib" "dir" "dis" "distutils" "divmod" "doctest"
- "dummy_threading" "elif" "else" "email" "enumerate" "ensurepip" "enum"
- "errno" "eval" "except" "exec" "execfile" "exit" "faulthandler" "fcntl"
- "file" "filecmp" "fileinput" "filter" "finally" "float" "fnmatch" "for"
- "format" "formatter" "fpectl" "fractions" "from" "frozenset" "ftplib"
- "functools" "gc" "getattr" "getopt" "getpass" "gettext" "glob" "global"
- "globals" "grp" "gzip" "hasattr" "hash" "hashlib" "heapq" "help" "hex"
- "hmac" "html" "http" "id" "if" "imghdr" "imp" "impalib" "import"
- "importlib" "in" "input" "inspect" "int" "intern" "io" "ipaddress" "is"
- "isinstance" "issubclass" "iter" "itertools" "json" "keyword" "lambda"
- "len" "license" "linecache" "list" "locale" "locals" "logging" "long"
- "lzma" "macpath" "mailbox" "mailcap" "map" "marshal" "math" "max"
- "memoryview" "mimetypes" "min" "mmap" "modulefinder" "msilib" "msvcrt"
- "multiprocessing" "netrc" "next" "nis" "nntplib" "not" "numbers" "object"
- "oct" "open" "operator" "optparse" "or" "ord" "os" "ossaudiodev" "parser"
- "pass" "pathlib" "pdb" "pickle" "pickletools" "pipes" "pkgutil" "platform"
- "plistlib" "poplib" "posix" "pow" "pprint" "print" "profile" "property"
- "pty" "pwd" "py_compiler" "pyclbr" "pydoc" "queue" "quit" "quopri" "raise"
- "random" "range" "raw_input" "re" "readline" "reduce" "reload" "repr"
- "reprlib" "resource" "return" "reversed" "rlcompleter" "round" "runpy"
- "sched" "select" "selectors" "self" "set" "setattr" "shelve" "shlex"
- "shutil" "signal" "site" "slice" "smtpd" "smtplib" "sndhdr" "socket"
- "socketserver" "sorted" "spwd" "sqlite3" "ssl" "stat" "staticmethod"
- "statistics" "str" "string" "stringprep" "struct" "subprocess" "sum"
- "sunau" "super" "symbol" "symtable" "sys" "sysconfig" "syslog" "tabnanny"
- "tarfile" "telnetlib" "tempfile" "termios" "test" "textwrap" "threading"
- "time" "timeit" "tkinter" "token" "tokenize" "trace" "traceback"
- "tracemalloc" "try" "tty" "tuple" "turtle" "type" "types" "unichr"
- "unicode" "unicodedata" "unittest" "urllib" "uu" "uuid" "vars" "venv"
- "warnings" "wave" "weakref" "webbrowser" "while" "winsound" "winreg" "with"
- "wsgiref" "xdrlib" "xml" "xmlrpc" "xrange" "yield" "zip" "zipfile" "zipimport"
- "zlib")
- (rust-mode
- "Self"
- "as" "box" "break" "const" "continue" "crate" "else" "enum" "extern"
- "false" "fn" "for" "if" "impl" "in" "let" "loop" "macro" "match" "mod"
- "move" "mut" "pub" "ref" "return" "self" "static" "struct" "super"
- "trait" "true" "type" "unsafe" "use" "where" "while")
- (swift-mode
- "true" "false" "nil" "#available" "#colorLiteral" "#column" "#else"
- "#elseif" "#endif" "#fileLiteral" "#file" "#function" "#if" "#imageLiteral"
- "#keypath" "#line" "#selector" "#sourceLocation" "associatedtype" "class"
- "deinit" "enum" "extension" "fileprivate" "func" "import" "init" "inout"
- "internal" "let" "open" "operator" "private" "protocol" "public" "static"
- "struct" "subscript" "typealias" "var" "break" "case" "continue" "default"
- "defer" "do" "else" "fallthrough" "for" "guard" "if" "in" "repeat" "return"
- "switch" "where" "while" "as" "catch" "dynamicType" "is" "rethrows" "super"
- "self" "Self" "throws" "throw" "try" "Protocol" "Type" "and" "assignment"
- "associativity" "convenience" "didSet" "dynamic" "final" "get" "higherThan"
- "indirect" "infix" "lazy" "left" "lowerThan" "mutating" "none"
- "nonmutating" "optional" "override" "postfix" "precedence"
- "precedencegroup" "prefix" "required" "right" "set" "unowned" "weak"
- "willSet"))
- "Alist mapping major-modes to keywords for.
-
- Keywords source: https://github.com/auto-complete/auto-complete/tree/master/dict
- and `company-keywords'.")
-
- (defvar ycmd--server-actual-port nil
- "The actual port being used by the ycmd server.
- This is set based on the value of `ycmd-server-port' if set, or
- the value from the output of the server itself.")
-
- (defvar ycmd--hmac-secret nil
- "This is populated with the hmac secret of the current connection.
- Users should never need to modify this, hence the defconst. It is
- not, however, treated as a constant by this code. This value gets
- set in ycmd-open.")
-
- (defconst ycmd--server-process-name "ycmd-server"
- "The Emacs name of the server process.
- This is used by functions like `start-process', `get-process'
- and `delete-process'.")
-
- (defvar-local ycmd--notification-timer nil
- "Timer for notifying ycmd server to do work, e.g. parsing files.")
-
- (defvar ycmd--keepalive-timer nil
- "Timer for sending keepalive messages to the server.")
-
- (defvar ycmd--on-focus-timer nil
- "Timer for deferring ycmd server notification to parse a buffer.")
-
- (defvar ycmd--server-timeout-timer nil)
-
- (defconst ycmd--server-buffer-name "*ycmd-server*"
- "Name of the ycmd server buffer.")
-
- (defvar-local ycmd--last-status-change 'unparsed
- "The last status of the current buffer.")
-
- (defvar-local ycmd--last-modified-tick nil
- "The BUFFER's last FileReadyToParse tick counter.")
-
- (defvar-local ycmd--buffer-visit-flag nil)
-
- (defvar ycmd--available-completers (make-hash-table :test 'eq))
-
- (defvar ycmd--process-environment nil)
-
- (defvar ycmd--mode-keywords-loaded nil
- "List of modes for which keywords have been loaded.")
-
- (defconst ycmd-hooks-alist
- '((after-save-hook . ycmd--on-save)
- (after-change-functions . ycmd--on-change)
- (window-configuration-change-hook . ycmd--on-window-configuration-change)
- (kill-buffer-hook . ycmd--on-close-buffer)
- (before-revert-hook . ycmd--teardown)
- (post-command-hook . ycmd--perform-deferred-parse))
- "Hooks which ycmd hooks in.")
-
- (add-hook 'kill-emacs-hook 'ycmd-close)
-
- (defvar ycmd-command-map
- (let ((map (make-sparse-keymap)))
- (define-key map "p" 'ycmd-parse-buffer)
- (define-key map "o" 'ycmd-open)
- (define-key map "c" 'ycmd-close)
- (define-key map "." 'ycmd-goto)
- (define-key map "gi" 'ycmd-goto-include)
- (define-key map "gd" 'ycmd-goto-definition)
- (define-key map "gD" 'ycmd-goto-declaration)
- (define-key map "gm" 'ycmd-goto-implementation)
- (define-key map "gp" 'ycmd-goto-imprecise)
- (define-key map "gr" 'ycmd-goto-references)
- (define-key map "gt" 'ycmd-goto-type)
- (define-key map "s" 'ycmd-toggle-force-semantic-completion)
- (define-key map "v" 'ycmd-show-debug-info)
- (define-key map "V" 'ycmd-version)
- (define-key map "d" 'ycmd-show-documentation)
- (define-key map "C" 'ycmd-clear-compilation-flag-cache)
- (define-key map "O" 'ycmd-restart-semantic-server)
- (define-key map "t" 'ycmd-get-type)
- (define-key map "T" 'ycmd-get-parent)
- (define-key map "f" 'ycmd-fixit)
- (define-key map "r" 'ycmd-refactor-rename)
- (define-key map "x" 'ycmd-completer)
- map)
- "Keymap for `ycmd-mode' interactive commands.")
-
- (defcustom ycmd-keymap-prefix (kbd "C-c Y")
- "Prefix for key bindings of `ycmd-mode'.
-
- Changing this variable outside Customize does not have any
- effect. To change the keymap prefix from Lisp, you need to
- explicitly re-define the prefix key:
-
- (define-key ycmd-mode-map ycmd-keymap-prefix nil)
- (setq ycmd-keymap-prefix (kbd \"C-c ,\"))
- (define-key ycmd-mode-map ycmd-keymap-prefix
- ycmd-command-map)"
- :type 'string
- :risky t
- :set
- (lambda (variable key)
- (when (and (boundp variable) (boundp 'ycmd-mode-map))
- (define-key ycmd-mode-map (symbol-value variable) nil)
- (define-key ycmd-mode-map key ycmd-command-map))
- (set-default variable key)))
-
- (defvar ycmd-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map ycmd-keymap-prefix ycmd-command-map)
- map)
- "Keymap for `ycmd-mode'.")
-
- (easy-menu-define ycmd-mode-menu ycmd-mode-map
- "Menu used when `ycmd-mode' is active."
- '("YCMd"
- ["Start server" ycmd-open]
- ["Stop server" ycmd-close]
- "---"
- ["Parse buffer" ycmd-parse-buffer]
- "---"
- ["GoTo" ycmd-goto]
- ["GoToDefinition" ycmd-goto-definition]
- ["GoToDeclaration" ycmd-goto-declaration]
- ["GoToInclude" ycmd-goto-include]
- ["GoToImplementation" ycmd-goto-implementation]
- ["GoToReferences" ycmd-goto-references]
- ["GoToType" ycmd-goto-type]
- ["GoToImprecise" ycmd-goto-imprecise]
- "---"
- ["Show documentation" ycmd-show-documentation]
- ["Show type" ycmd-get-type]
- ["Show parent" ycmd-get-parent]
- "---"
- ["FixIt" ycmd-fixit]
- ["RefactorRename" ycmd-refactor-rename]
- "---"
- ["Load extra config" ycmd-load-conf-file]
- ["Restart semantic server" ycmd-restart-semantic-server]
- ["Clear compilation flag cache" ycmd-clear-compilation-flag-cache]
- ["Force semantic completion" ycmd-toggle-force-semantic-completion
- :style toggle :selected ycmd-force-semantic-completion]
- "---"
- ["Show debug info" ycmd-show-debug-info]
- ["Show version" ycmd-version t]
- ["Log enabled" ycmd-toggle-log-enabled
- :style toggle :selected ycmd--log-enabled]))
-
- (defmacro ycmd--kill-timer (timer)
- "Cancel TIMER."
- `(when ,timer
- (cancel-timer ,timer)
- (setq ,timer nil)))
-
- (defun ycmd-parsing-in-progress-p ()
- "Return t if parsing is in progress."
- (eq ycmd--last-status-change 'parsing))
-
- (defun ycmd--report-status (status)
- "Report ycmd STATUS."
- (setq ycmd--last-status-change status)
- (force-mode-line-update))
-
- (defun ycmd--mode-line-status-text ()
- "Get text for the mode line."
- (let ((force-semantic
- (when ycmd-force-semantic-completion "/s"))
- (text (pcase ycmd--last-status-change
- (`parsed "")
- (`parsing "*")
- (`unparsed "?")
- (`stopped "-")
- (`starting ">")
- (`errored "!"))))
- (concat " " ycmd-mode-line-prefix force-semantic text)))
-
- ;;;###autoload
- (define-minor-mode ycmd-mode
- "Minor mode for interaction with the ycmd completion server.
-
- When called interactively, toggle `ycmd-mode'. With prefix ARG,
- enable `ycmd-mode' if ARG is positive, otherwise disable it.
-
- When called from Lisp, enable `ycmd-mode' if ARG is omitted,
- nil or positive. If ARG is `toggle', toggle `ycmd-mode'.
- Otherwise behave as if called interactively.
-
- \\{ycmd-mode-map}"
- :init-value nil
- :keymap ycmd-mode-map
- :lighter (:eval (ycmd--mode-line-status-text))
- :group 'ycmd
- :require 'ycmd
- :after-hook
- (progn (unless (ycmd-running-p) (ycmd-open))
- (ycmd--conditional-parse 'mode-enabled 'deferred))
- (cond
- (ycmd-mode
- (dolist (hook ycmd-hooks-alist)
- (add-hook (car hook) (cdr hook) nil 'local)))
- (t
- (dolist (hook ycmd-hooks-alist)
- (remove-hook (car hook) (cdr hook) 'local))
- (ycmd--teardown))))
-
- ;;;###autoload
- (defun ycmd-setup ()
- "Setup `ycmd-mode'.
-
- Hook `ycmd-mode' into modes in `ycmd-file-type-map'."
- (interactive)
- (dolist (it ycmd-file-type-map)
- (add-hook (intern (format "%s-hook" (symbol-name (car it)))) 'ycmd-mode)))
- (make-obsolete 'ycmd-setup 'global-ycmd-mode "1.0")
-
- (defun ycmd-version (&optional show-version)
- "Get the `emacs-ycmd' version as string.
-
- If called interactively or if SHOW-VERSION is non-nil, show the
- version in the echo area and the messages buffer.
-
- The returned string includes both, the version from package.el
- and the library version, if both a present and different.
-
- If the version number could not be determined, signal an error,
- if called interactively, or if SHOW-VERSION is non-nil, otherwise
- just return nil."
- (interactive (list t))
- (if (require 'pkg-info nil :no-error)
- (let ((version (pkg-info-version-info 'ycmd)))
- (when show-version
- (message "emacs-ycmd version: %s" version))
- version)
- (error "Cannot determine version without package pkg-info")))
-
- (defun ycmd--maybe-enable-mode ()
- "Enable `ycmd-mode' according `ycmd-global-modes'."
- (when (pcase ycmd-global-modes
- (`t (ycmd-major-mode-to-file-types major-mode))
- (`all t)
- (`(not . ,modes) (not (memq major-mode modes)))
- (modes (memq major-mode modes)))
- (ycmd-mode)))
-
- ;;;###autoload
- (define-globalized-minor-mode global-ycmd-mode ycmd-mode
- ycmd--maybe-enable-mode
- :init-value nil)
-
- (defun ycmd-unload-function ()
- "Unload function for ycmd."
- (global-ycmd-mode -1)
- (remove-hook 'kill-emacs-hook #'ycmd-close))
-
- (defvar-local ycmd--deferred-parse nil
- "If non-nil, a deferred file parse notification is pending.")
-
- (defun ycmd--must-defer-parse ()
- "Determine whether parsing the file has to be deferred.
-
- Return t if parsing is to be deferred, or nil otherwise."
- (or (not (ycmd--server-alive-p))
- (not (get-buffer-window))
- (ycmd-parsing-in-progress-p)
- revert-buffer-in-progress-p))
-
- (defun ycmd--deferred-parse-p ()
- "Return non-nil if current buffer has a deferred parse."
- ycmd--deferred-parse)
-
- (defun ycmd--parse-deferred ()
- "Defer parse notification for current buffer."
- (setq ycmd--deferred-parse t))
-
- (defun ycmd--perform-deferred-parse ()
- "Perform the deferred parse."
- (when (ycmd--deferred-parse-p)
- (setq ycmd--deferred-parse nil)
- (ycmd--conditional-parse)))
-
- (defun ycmd--conditional-parse (&optional condition force-deferred)
- "Reparse the buffer under CONDITION.
-
- If CONDITION is non-nil, determine whether a ready to parse
- notification should be sent according `ycmd-parse-conditions'.
-
- If FORCE-DEFERRED is non-nil perform parse notification later."
- (when (and ycmd-mode
- (or (not condition)
- (memq condition ycmd-parse-conditions)))
- (if (or force-deferred (ycmd--must-defer-parse))
- (ycmd--parse-deferred)
- (let ((buffer (current-buffer)))
- (deferred:$
- (deferred:next
- (lambda ()
- (with-current-buffer buffer
- (ycmd--on-visit-buffer))))
- (deferred:nextc it
- (lambda ()
- (with-current-buffer buffer
- (let ((tick (buffer-chars-modified-tick)))
- (unless (equal tick ycmd--last-modified-tick)
- (setq ycmd--last-modified-tick tick)
- (ycmd-notify-file-ready-to-parse)))))))))))
-
- (defun ycmd--on-save ()
- "Function to run when the buffer has been saved."
- (ycmd--conditional-parse 'save))
-
- (defun ycmd--on-idle-change ()
- "Function to run on idle-change."
- (ycmd--kill-timer ycmd--notification-timer)
- (ycmd--conditional-parse 'idle-change))
-
- (defun ycmd--on-change (beg end _len)
- "Function to run when a buffer change between BEG and END.
- _LEN is ununsed."
- (save-match-data
- (when ycmd-mode
- (ycmd--kill-timer ycmd--notification-timer)
- (if (string-match-p "\n" (buffer-substring beg end))
- (ycmd--conditional-parse 'new-line)
- (setq ycmd--notification-timer
- (run-at-time ycmd-idle-change-delay nil
- #'ycmd--on-idle-change))))))
-
- (defun ycmd--on-unparsed-buffer-focus (buffer)
- "Function to run when an unparsed BUFFER gets focus."
- (ycmd--kill-timer ycmd--on-focus-timer)
- (with-current-buffer buffer
- (ycmd--conditional-parse 'buffer-focus)))
-
- (defun ycmd--on-window-configuration-change ()
- "Function to run by `window-configuration-change-hook'."
- (if (ycmd--deferred-parse-p)
- (ycmd--perform-deferred-parse)
- (when (and ycmd-mode
- (pcase ycmd--last-status-change
- ((or `unparsed `starting) t))
- (memq 'buffer-focus ycmd-parse-conditions))
- (ycmd--kill-timer ycmd--on-focus-timer)
- (let ((on-buffer-focus-fn
- (apply-partially 'ycmd--on-unparsed-buffer-focus
- (current-buffer))))
- (setq ycmd--on-focus-timer
- (run-at-time 1.0 nil on-buffer-focus-fn))))))
-
- (defmacro ycmd--with-all-ycmd-buffers (&rest body)
- "Execute BODY with each `ycmd-mode' enabled buffer."
- (declare (indent 0) (debug t))
- `(dolist (buffer (buffer-list))
- (with-current-buffer buffer
- (when ycmd-mode
- ,@body))))
-
- (defun ycmd--exception-p (response)
- "Check whether RESPONSE is an exception."
- (and (listp response) (assq 'exception response)))
-
- (cl-defmacro ycmd-with-handled-server-exceptions (request
- &rest body
- &key
- dont-show-exception-msg
- on-exception-form
- return-form
- bind-current-buffer
- &allow-other-keys)
- "Run a deferred REQUEST and exectute BODY on success. Catch all
- exceptions raised through server communication. If it is raised
- because of a unknown .ycm_extra_conf.py file, load the file or
- ignore it after asking the user. Otherwise print exception to
- minibuffer if NO-EXCEPTION-MESSAGE is nil. ON-EXCEPTION-FORM is
- run if an exception occurs. The value of RETURN-FORM is returned
- on exception. If BIND-CURRENT-BUFFER is non-nil, bind
- `current-buffer' to `request-buffer' var.
-
- \(fn REQUEST &key NO-DISPLAY ERROR-FORM RETURN-FORM
- BIND-CURRENT-BUFFER &rest BODY)"
- (declare (indent 1) (debug t))
- (while (keywordp (car body))
- (setq body (cdr (cdr body))))
- `(let ((request-buffer (and ,bind-current-buffer (current-buffer))))
- (deferred:$
- ,request
- (deferred:nextc it
- (lambda (response)
- (cl-macrolet ((with-optional-current-buffer
- (buffer-or-name &rest body-2)
- `(if ,buffer-or-name
- (with-current-buffer ,buffer-or-name
- ,@body-2)
- ,@body-2)))
- (with-optional-current-buffer
- request-buffer
- (if (ycmd--exception-p response)
- (let-alist response
- (if (string= .exception.TYPE "UnknownExtraConf")
- (ycmd--handle-extra-conf-exception
- .exception.extra_conf_file)
- (unless ,dont-show-exception-msg
- (message "%s: %s" .exception.TYPE .message))
- ,on-exception-form
- ,return-form))
- (if (null ',body) response ,@body)))))))))
-
- (defun ycmd--on-visit-buffer ()
- "If `ycmd--buffer-visit-flag' is nil send BufferVisit event."
- (when (and (not ycmd--buffer-visit-flag)
- (ycmd--server-alive-p))
- (ycmd-with-handled-server-exceptions
- (ycmd--event-notification "BufferVisit")
- :bind-current-buffer t
- (setq ycmd--buffer-visit-flag t))))
-
- (defun ycmd--on-close-buffer ()
- "Notify server that the current buffer is no longer open.
- Cleanup emacs-ycmd variables."
- (when (ycmd--server-alive-p)
- (ycmd-with-handled-server-exceptions
- (ycmd--event-notification "BufferUnload")))
- (ycmd--teardown))
-
- (defun ycmd--reset-parse-status ()
- (ycmd--report-status 'unparsed)
- (setq ycmd--last-modified-tick nil))
-
- (defun ycmd--teardown ()
- "Teardown ycmd in current buffer."
- (ycmd--kill-timer ycmd--notification-timer)
- (ycmd--reset-parse-status)
- (setq ycmd--deferred-parse nil)
- (run-hooks 'ycmd-after-teardown-hook))
-
- (defun ycmd--global-teardown ()
- "Teardown ycmd in all buffers."
- (ycmd--kill-timer ycmd--on-focus-timer)
- (setq ycmd--mode-keywords-loaded nil)
- (clrhash ycmd--available-completers)
- (ycmd--with-all-ycmd-buffers
- (ycmd--teardown)
- (setq ycmd--buffer-visit-flag nil)))
-
- (defun ycmd-file-types-with-diagnostics (mode)
- "Find the ycmd file types for MODE which support semantic diagnostics.
-
- Returns a possibly empty list of ycmd file type strings. If this
- is empty, then ycmd doesn't support semantic completion (or
- diagnostics) for MODE."
- (-intersection
- ycmd--file-types-with-diagnostics
- (ycmd-major-mode-to-file-types mode)))
-
- (defun ycmd-major-modes-with-diagnostics ()
- "Return a list with major-modes which support semantic diagnostics."
- (->>
- (--filter (member (cadr it) ycmd--file-types-with-diagnostics)
- ycmd-file-type-map)
- (--map (car it))))
-
- (defun ycmd-deferred:sync! (d)
- "Wait for the given deferred task.
- Error is raised if it is not processed within deferred chain D.
- This is a slightly modified version of the original
- `deferred:sync!' function, with using `accept-process-output'
- wrapped with `with-current-buffer' for waiting instead of a
- combination of `sit-for' and `sleep-for' and with a shorter wait
- time."
- (progn
- (let ((last-value 'deferred:undefined*)
- uncaught-error)
- (deferred:try
- (deferred:nextc d
- (lambda (x) (setq last-value x)))
- :catch
- (lambda (err) (setq uncaught-error err)))
- (with-local-quit
- (while (and (eq 'deferred:undefined* last-value)
- (not uncaught-error))
- (accept-process-output nil 0.01))
- (when uncaught-error
- (deferred:resignal uncaught-error))
- last-value))))
-
- (defmacro ycmd-deferred:timeout (timeout-sec d)
- "Time out macro on a deferred task.
- If the deferred task does not complete within TIMEOUT-SEC, this
- macro cancels the deferred task D and returns nil. This is a
- slightly modified version of the original `deferred:timeout'
- macro, which takes the timeout var in seconds and the timeout
- form returns the symbol `timeout'."
- (declare (indent 1) (debug t))
- `(deferred:earlier
- (deferred:nextc (deferred:wait (* ,timeout-sec 1000))
- (lambda () 'timeout))
- ,d))
-
- (defun ycmd-open ()
- "Start a new ycmd server.
-
- This kills any ycmd server already running (under ycmd.el's
- control.)."
- (interactive)
- (ycmd-close)
- (ycmd--start-server)
- (ycmd--start-server-timeout-timer)
- (ycmd--start-keepalive-timer))
-
- (defun ycmd-close (&optional status)
- "Shutdown any running ycmd server.
- STATUS is a status symbol for `ycmd--report-status',
- defaulting to `stopped'."
- (interactive)
- (ycmd--stop-server)
- (ycmd--global-teardown)
- (ycmd--kill-timer ycmd--keepalive-timer)
- (ycmd--kill-timer ycmd--server-timeout-timer)
- (ycmd--with-all-ycmd-buffers
- (ycmd--report-status (or status 'stopped))))
-
- (defun ycmd--stop-server ()
- "Stop the ycmd server process.
-
- Send a `shutdown' request to the ycmd server and wait for the
- ycmd server to stop. If the ycmd server is still running after a
- timeout specified by `ycmd-delete-process-delay', then kill the
- process with `delete-process'."
- (when (ycmd--server-alive-p)
- (let ((start-time (float-time)))
- (ycmd-deferred:sync!
- (ycmd-deferred:timeout 0.1
- (ycmd-with-handled-server-exceptions
- (ycmd--request (make-ycmd-request-data
- :handler "shutdown" :content nil))
- :dont-show-exception-msg t)))
- (while (and (ycmd-running-p)
- (> ycmd-delete-process-delay
- (- (float-time) start-time)))
- (sit-for 0.05))))
- (ignore-errors
- (delete-process ycmd--server-process-name)))
-
- (defun ycmd-running-p ()
- "Return t if a ycmd server is already running."
- (--when-let (get-process ycmd--server-process-name)
- (and (processp it) (process-live-p it) t)))
-
- (defun ycmd--server-alive-p ()
- "Return t if server is running and ready for requests."
- (and (ycmd-running-p) ycmd--server-actual-port))
-
- (defmacro ycmd--ignore-errors (&rest body)
- "Execute BODY and ignore errors and request errors."
- `(let ((request-message-level -1)
- (request-log-level -1))
- (ignore-errors
- ,@body)))
-
- (defun ycmd--keepalive ()
- "Sends an unspecified message to the server.
-
- This is simply for keepalive functionality."
- (ycmd--ignore-errors
- (ycmd-with-handled-server-exceptions
- (ycmd--request (make-ycmd-request-data
- :handler "healthy" :content nil)
- :type "GET")
- :dont-show-exception-msg t)))
-
- (defun ycmd--server-ready-p (&optional include-subserver)
- "Send request for server ready state.
-
- If INCLUDE-SUBSERVER is non-nil, also request ready state for
- semantic subserver."
- (when (ycmd--server-alive-p)
- (let* ((file-type
- (and include-subserver
- (car-safe (ycmd-major-mode-to-file-types
- major-mode))))
- (params (and file-type
- (list (cons "subserver" file-type)))))
- (ycmd--ignore-errors
- (eq (ycmd-deferred:sync!
- (ycmd-with-handled-server-exceptions
- (ycmd--request
- (make-ycmd-request-data :handler "ready" :content nil)
- :params params :type "GET")
- :dont-show-exception-msg t))
- t)))))
-
- (defun ycmd--extra-conf-request (filename &optional ignore-p)
- "Send extra conf request.
- FILENAME is the path to a ycm_extra_conf file. If optional
- IGNORE-P is non-nil ignore the ycm_extra_conf."
- (let ((handler (if ignore-p
- "ignore_extra_conf_file"
- "load_extra_conf_file"))
- (content (list (cons "filepath" filename))))
- (ycmd-deferred:sync!
- (ycmd-with-handled-server-exceptions
- (ycmd--request (make-ycmd-request-data
- :handler handler :content content))))))
-
- (defun ycmd-load-conf-file (filename)
- "Tell the ycmd server to load the configuration file FILENAME."
- (interactive
- (list
- (read-file-name "Filename: ")))
- (let ((filename (expand-file-name filename)))
- (ycmd--extra-conf-request filename)))
-
- (defun ycmd-display-completions ()
- "Get completions at the current point and display them in a buffer.
-
- This is really a utility/debugging function for developers, but
- it might be interesting for some users."
- (interactive)
- (ycmd-with-handled-server-exceptions (ycmd-get-completions)
- (if (not response)
- (message "No completions available")
- (pop-to-buffer "*ycmd-completions*")
- (erase-buffer)
- (insert (pp-to-string response)))))
-
- (defun ycmd-complete (&optional _ignored)
- "Completion candidates at point."
- (-when-let* ((completions (ycmd-deferred:sync!
- (ycmd-deferred:timeout 0.5
- (ycmd-with-handled-server-exceptions
- (ycmd-get-completions)))))
- (candidates (cdr (assq 'completions completions))))
- (--map (let* ((text (cdr (assq 'insertion_text it)))
- (anno (cdr (assq 'menu_text it))))
- (when (and anno (string-match (regexp-quote text) anno))
- (put-text-property
- 0 1 'anno (substring anno (match-end 0)) text))
- text)
- candidates)))
-
- (defun ycmd-complete-at-point ()
- "Complete symbol at point."
- (unless (nth 3 (syntax-ppss)) ;; not in string
- (let* ((bounds (bounds-of-thing-at-point 'symbol))
- (beg (or (car bounds) (point)))
- (end (or (cdr bounds) (point))))
- (list beg end
- (completion-table-dynamic #'ycmd-complete)
- :annotation-function
- (lambda (arg) (get-text-property 0 'anno arg))))))
-
- (defun ycmd-toggle-force-semantic-completion ()
- "Toggle whether to use always semantic completion.
-
- Returns the new value of `ycmd-force-semantic-completion'."
- (interactive)
- (let ((force (not ycmd-force-semantic-completion)))
- (message "ycmd: force semantic completion %s."
- (if force "enabled" "disabled"))
- (setq ycmd-force-semantic-completion force)))
-
- (defun ycmd--string-list-p (obj)
- "Return t if OBJ is a list of strings."
- (and (listp obj) (-all? #'stringp obj)))
-
- (defun ycmd--locate-default-tags-file (buffer)
- "Look up directory hierarchy for first found default tags file for BUFFER."
- (-when-let* ((file (buffer-file-name buffer))
- (dir (and file
- (locate-dominating-file
- file ycmd-default-tags-file-name))))
- (expand-file-name ycmd-default-tags-file-name dir)))
-
- (defun ycmd--get-tag-files (buffer)
- "Get tag files list for current BUFFER or nil."
- (--when-let (cond ((eq ycmd-tag-files 'auto)
- (ycmd--locate-default-tags-file buffer))
- ((or (stringp ycmd-tag-files)
- (ycmd--string-list-p ycmd-tag-files))
- ycmd-tag-files))
- (unless (listp it)
- (setq it (list it)))
- (mapcar 'expand-file-name it)))
-
- (defun ycmd--get-keywords (buffer)
- "Get syntax keywords for BUFFER."
- (with-current-buffer buffer
- (let ((mode major-mode))
- (unless (memq mode ycmd--mode-keywords-loaded)
- (--when-let (and (functionp ycmd-get-keywords-function)
- (funcall ycmd-get-keywords-function mode))
- (when (ycmd--string-list-p it)
- (add-to-list 'ycmd--mode-keywords-loaded mode)
- it))))))
-
- (defun ycmd--get-keywords-from-alist (mode)
- "Get keywords from `ycmd-keywords-alist' for MODE."
- (let ((symbols (cdr (assq mode ycmd-keywords-alist))))
- (if (consp symbols)
- symbols
- (cdr (assq symbols ycmd-keywords-alist)))))
-
- (defun ycmd-get-completions ()
- "Get completions in current buffer from the ycmd server.
-
- Returns a deferred object which yields the HTTP message
- content. If completions are available, the structure looks like
- this:
-
- ((error)
- (completion_start_column . 6)
- (completions
- ((kind . \"FUNCTION\")
- (extra_menu_info . \"long double\")
- (detailed_info . \"long double acoshl( long double )\\n\")
- (insertion_text . \"acoshl\")
- (menu_text . \"acoshl( long double )\"))
- . . .))
-
- If ycmd can't do completion because it's busy parsing, the
- structure looks like this:
-
- ((message . \"Still parsing file, no completions yet.\")
- (traceback . \"long traceback string\")
- (exception
- (TYPE . \"RuntimeError\")))
-
- To see what the returned structure looks like, you can use
- `ycmd-display-completions'."
- (let ((extra-data (and ycmd-force-semantic-completion
- (list (cons "force_semantic" t)))))
- (ycmd--request (make-ycmd-request-data
- :handler "completions"
- :content (append (ycmd--get-basic-request-data)
- extra-data)))))
-
- (defun ycmd--command-request (subcommand)
- "Send a command request for SUBCOMMAND."
- (let* ((subcommand (if (listp subcommand)
- subcommand
- (list subcommand)))
- (content (cons (append (list "command_arguments")
- subcommand)
- (ycmd--get-basic-request-data))))
- (ycmd--request (make-ycmd-request-data
- :handler "run_completer_command"
- :content content))))
-
- (defun ycmd--run-completer-command (subcommand success-handler)
- "Send SUBCOMMAND to the `ycmd' server.
-
- SUCCESS-HANDLER is called when for a successful response."
- (declare (indent 1))
- (when ycmd-mode
- (let ((cmd (or (car-safe subcommand) subcommand)))
- (if (ycmd-parsing-in-progress-p)
- (message "Can't send \"%s\" request while parsing is in progress!" cmd)
- (let ((pos (point)))
- (ycmd-with-handled-server-exceptions (ycmd--command-request subcommand)
- :bind-current-buffer t
- :on-exception-form (run-hook-with-args 'ycmd-after-exception-hook
- cmd request-buffer pos response)
- (when (and response success-handler)
- (funcall success-handler response))))))))
-
- (defun ycmd--unsupported-subcommand-p (response)
- "Return t if RESPONSE is an unsupported subcommand exception."
- (let-alist response
- (and (string= "ValueError" .exception.TYPE)
- (or (string-prefix-p "Supported commands are:\n" .message)
- (string= "This Completer has no supported subcommands."
- .message)))))
-
- (defun ycmd--get-defined-subcommands ()
- "Get available subcommands for current completer.
- This is a blocking request."
- (let* ((data (make-ycmd-request-data
- :handler "defined_subcommands")))
- (ycmd-deferred:sync!
- (ycmd-with-handled-server-exceptions (ycmd--request data)))))
-
- (defun ycmd--get-prompt-for-subcommand (subcommand)
- "Return promp for SUBCOMMAND that requires arguments."
- (pcase subcommand
- (`"RefactorRename"
- "New name: ")
- ((pred (and "RestartServer" (eq major-mode 'python-mode)))
- "Python binary: ")))
-
- (defun ycmd--read-subcommand ()
- "Read subcommand from minibuffer."
- (--when-let (ycmd--get-defined-subcommands)
- (funcall ycmd-completing-read-function
- "Subcommand: " it nil t)))
-
- (defun ycmd-completer (subcommand)
- "Run SUBCOMMAND for current completer."
- (interactive
- (list (-when-let (cmd (ycmd--read-subcommand))
- (--when-let (ycmd--get-prompt-for-subcommand cmd)
- (let ((arg (read-string it)))
- (unless (s-blank-str? arg)
- (setq cmd (list cmd arg)))))
- cmd)))
- (when subcommand
- (ycmd--run-completer-command subcommand
- (lambda (response)
- (cond ((not (listp response))
- ;; If not a list, the response is necessarily a scalar:
- ;; boolean, number, string, etc. In this case, we print it to
- ;; the user.
- (message "%s" response))
- ((assq 'fixits response)
- (ycmd--handle-fixit-response response))
- ((assq 'message response)
- (ycmd--handle-message-response response))
- ((assq 'detailed_info response)
- (ycmd--handle-detailed-info-response response))
- (t
- (ycmd--handle-goto-response response)))))))
-
- (defun ycmd-goto ()
- "Go to the definition or declaration of the symbol at current position."
- (interactive)
- (ycmd--goto "GoTo"))
-
- (defun ycmd-goto-declaration ()
- "Go to the declaration of the symbol at the current position."
- (interactive)
- (ycmd--goto "GoToDeclaration"))
-
- (defun ycmd-goto-definition ()
- "Go to the definition of the symbol at the current position."
- (interactive)
- (ycmd--goto "GoToDefinition"))
-
- (defun ycmd-goto-implementation ()
- "Go to the implementation of the symbol at the current position."
- (interactive)
- (ycmd--goto "GoToImplementation"))
-
- (defun ycmd-goto-include ()
- "Go to the include of the symbol at the current position."
- (interactive)
- (ycmd--goto "GoToInclude"))
-
- (defun ycmd-goto-imprecise ()
- "Fast implementation of Go To at the cost of precision.
- Useful in case compile-time is considerable."
- (interactive)
- (ycmd--goto "GoToImprecise"))
-
- (defun ycmd-goto-references ()
- "Get references."
- (interactive)
- (ycmd--goto "GoToReferences"))
-
- (defun ycmd-goto-type ()
- "Go to the type of the symbol at the current position."
- (interactive)
- (ycmd--goto "GoToType"))
-
- (defun ycmd--save-marker ()
- "Save marker."
- (push-mark)
- (if (fboundp 'xref-push-marker-stack)
- (xref-push-marker-stack)
- (with-no-warnings
- (ring-insert find-tag-marker-ring (point-marker)))))
-
- (defun ycmd--location-data-p (response)
- "Return t if RESPONSE is a GoTo location."
- (and (assq 'filepath response)
- (assq 'line_num response)
- (assq 'column_num response)))
-
- (defun ycmd--handle-goto-response (response)
- "Handle a successfull GoTo RESPONSE."
- (ycmd--save-marker)
- (if (ycmd--location-data-p response)
- (ycmd--goto-location response 'find-file)
- (ycmd--view response major-mode)))
-
- (defun ycmd--goto (type)
- "Implementation of GoTo according to the request TYPE."
- (save-excursion
- (--when-let (bounds-of-thing-at-point 'symbol)
- (goto-char (car it)))
- (ycmd-completer type)))
-
- (defun ycmd--goto-location (location find-function)
- "Move cursor to LOCATION with FIND-FUNCTION.
-
- LOCATION is a structure as returned from e.g. the various GoTo
- commands."
- (let-alist location
- (when .filepath
- (funcall find-function .filepath)
- (goto-char (ycmd--col-line-to-position
- .column_num .line_num)))))
-
- (defun ycmd--goto-line (line)
- "Go to LINE."
- (goto-char (point-min))
- (forward-line (1- line)))
-
- (defun ycmd--col-line-to-position (col line &optional buffer)
- "Convert COL and LINE into a position in the current buffer.
-
- COL and LINE are expected to be as returned from ycmd, e.g. from
- notify-file-ready. Apparently COL can be 0 sometimes, in which
- case this function returns 0.
- Use BUFFER if non-nil or `current-buffer'."
- (let ((buff (or buffer (current-buffer))))
- (if (= col 0)
- 0
- (with-current-buffer buff
- (ycmd--goto-line line)
- (forward-char (- col 1))
- (point)))))
-
- (defun ycmd-clear-compilation-flag-cache ()
- "Clear the compilation flags cache."
- (interactive)
- (ycmd-completer "ClearCompilationFlagCache"))
-
- (defun ycmd-restart-semantic-server (&optional arg)
- "Send request to restart the semantic completion backend server.
- If ARG is non-nil and current `major-mode' is `python-mode',
- prompt for the Python binary."
- (interactive
- (list (and current-prefix-arg
- (eq major-mode 'python-mode)
- (read-string "Python binary: "))))
- (let ((subcommand "RestartServer"))
- (unless (s-blank-str? arg)
- (setq subcommand (list subcommand arg)))
- (ycmd-completer subcommand)))
-
- (cl-defun ycmd--fontify-code (code &optional (mode major-mode))
- "Fontify CODE."
- (cl-check-type mode function)
- (if (not (stringp code))
- code
- (with-temp-buffer
- (delay-mode-hooks (funcall mode))
- (setq font-lock-mode t)
- (funcall font-lock-function font-lock-mode)
- (let ((inhibit-read-only t))
- (erase-buffer)
- (insert code)
- (font-lock-default-fontify-region
- (point-min) (point-max) nil))
- (buffer-string))))
-
- (defun ycmd--get-message (response)
- "Extract message from RESPONSE.
- Return a cons cell with the type or parent as car. If cdr is
- non-nil, the result is a valid type or parent."
- (--when-let (cdr (assq 'message response))
- (pcase it
- ((or `"Unknown semantic parent"
- `"Unknown type"
- `"Internal error: cursor not valid"
- `"Internal error: no translation unit")
- (cons it nil))
- (_ (cons it t)))))
-
- (defun ycmd--handle-message-response (response)
- "Handle a successful GetParent or GetType RESPONSE."
- (--when-let (ycmd--get-message response)
- (pcase-let ((`(,msg . ,is-type-p) it))
- (message "%s" (if is-type-p
- (ycmd--fontify-code msg)
- msg)))))
-
- (defun ycmd-get-parent ()
- "Get semantic parent for symbol at point."
- (interactive)
- (ycmd-completer "GetParent"))
-
- (defun ycmd-get-type (&optional arg)
- "Get type for symbol at point.
- If optional ARG is non-nil, get type without reparsing buffer."
- (interactive "P")
- (ycmd-completer (if arg "GetTypeImprecise" "GetType")))
-
- ;;; FixIts
-
- (defmacro ycmd--loop-chunks-by-filename (spec &rest body)
- "Loop over an alist of fixit chunks grouped by filepath.
- Evaluate BODY with `it' bound to each car from FIXIT-CHUNKS, in
- turn. The structure of `it' is a cons cell (FILEPATH CHUNK-LIST).
- Then evaluate RESULT to get return value, default nil.
-
- \(fn (FIXIT-CHUNKS [RESULT]) BODY...)"
- (declare (indent 1) (debug ((form &optional form) body)))
- `(let ((chunks-by-filepath
- (--group-by (let-alist it .range.start.filepath)
- ,(car spec))))
- (dolist (it chunks-by-filepath)
- ,@body)
- ,@(cdr spec)))
-
- (defun ycmd--show-fixits (fixits &optional title)
- "Select a buffer and display FIXITS.
- Optional TITLE is shown on first line."
- (let ((fixits-buffer (get-buffer-create "*ycmd-fixits*"))
- (fixit-num 1))
- (with-current-buffer fixits-buffer
- (setq buffer-read-only nil)
- (erase-buffer)
- (when title (insert (propertize title 'face 'bold)))
- (dolist (fixit fixits)
- (let-alist fixit
- (let* ((diffs (ycmd--get-fixit-diffs .chunks))
- (multiple-fixits-p (> (length diffs) 1))
- button-diff)
- (dolist (diff diffs)
- (pcase-let ((`(,diff-text . ,diff-path) diff))
- (setq button-diff
- (concat button-diff (when (or (s-blank-str? .text)
- multiple-fixits-p)
- (format "%s\n" diff-path))
- (ycmd--fontify-code diff-text 'diff-mode) "\n"))))
- (ycmd--insert-fixit-button
- (concat (format "%d: %s\n" fixit-num .text) button-diff) .chunks)
- (cl-incf fixit-num))))
- (goto-char (point-min))
- (when title (forward-line 1))
- (ycmd-fixit-mode))
- (pop-to-buffer fixits-buffer)
- (setq next-error-last-buffer fixits-buffer)))
-
- (defun ycmd--get-fixit-diffs (chunks)
- "Return a list of diffs for CHUNKS.
- Each diff is a list of the actual diff, the path of the file for
- the diff and a flag whether to show the filepath as part of the
- button text. The flag is set to t when there are multiple diff
- chunks for the file."
- (let (diffs)
- (ycmd--loop-chunks-by-filename (chunks (nreverse diffs))
- (pcase-let* ((`(,filepath . ,chunk) it)
- (buffer (find-file-noselect filepath))
- (buffertext (with-current-buffer buffer (buffer-string)))
- (diff-buffer (with-temp-buffer
- (insert buffertext)
- (ycmd--replace-chunk-list chunk (current-buffer))
- (diff-no-select buffer (current-buffer)
- "-U0 --strip-trailing-cr" t))))
- (with-current-buffer diff-buffer
- (goto-char (point-min))
- (unless (eobp)
- (ignore-errors
- (diff-beginning-of-hunk t))
- (while (looking-at diff-hunk-header-re-unified)
- (let* ((beg (point))
- (end (diff-end-of-hunk))
- (diff (buffer-substring-no-properties beg end)))
- (push (cons diff filepath) diffs)))))))))
-
- (define-button-type 'ycmd--fixit-button
- 'action #'ycmd--apply-fixit
- 'face nil)
-
- (defun ycmd--insert-fixit-button (name fixit)
- "Insert a button with NAME and FIXIT."
- (insert-text-button
- name
- 'type 'ycmd--fixit-button
- 'fixit fixit))
-
- (defun ycmd--apply-fixit (button)
- "Apply BUTTON's FixIt chunk."
- (-when-let (chunks (button-get button 'fixit))
- (ycmd--loop-chunks-by-filename (chunks)
- (ycmd--replace-chunk-list (cdr it)))
- (quit-window t (get-buffer-window "*ycmd-fixits*"))))
-
- (define-derived-mode ycmd-fixit-mode ycmd-view-mode "ycmd-fixits"
- "Major mode for viewing and navigation of fixits.
-
- \\{ycmd-view-mode-map}"
- (local-set-key (kbd "q") (lambda () (interactive) (quit-window t))))
-
- (defun ycmd--replace-chunk (bounds replacement-text line-delta char-delta buffer)
- "Replace text between BOUNDS with REPLACEMENT-TEXT.
-
- BOUNDS is a list of two cons cells representing the start and end
- of a chunk with a line and column pair (car and cdr). LINE-DELTA
- and CHAR-DELTA are offset from -former replacements on the
- current line. BUFFER is the current working buffer."
- (pcase-let* ((`((,start-line . ,start-column) (,end-line . ,end-column)) bounds)
- (start-line (+ start-line line-delta))
- (end-line (+ end-line line-delta))
- (source-line-count (1+ (- end-line start-line)))
- (start-column (+ start-column char-delta))
- (end-column (if (= source-line-count 1)
- (+ end-column char-delta)
- end-column))
- (replacement-lines (s-split "\n" replacement-text))
- (replacement-lines-count (length replacement-lines))
- (new-line-delta (- replacement-lines-count source-line-count))
- (new-char-delta (- (length (car (last replacement-lines)))
- (- end-column start-column))))
- (when (> replacement-lines-count 1)
- (setq new-char-delta (- new-char-delta start-column)))
- (save-excursion
- (with-current-buffer buffer
- (delete-region
- (ycmd--col-line-to-position start-column start-line buffer)
- (ycmd--col-line-to-position end-column end-line buffer))
- (insert replacement-text)
- (cons new-line-delta new-char-delta)))))
-
- (defun ycmd--get-chunk-bounds (chunk)
- "Get an list with bounds of CHUNK."
- (let-alist chunk
- (list (cons .range.start.line_num .range.start.column_num)
- (cons .range.end.line_num .range.end.column_num))))
-
- (defun ycmd--chunk-< (c1 c2)
- "Return t if C1 should go before C2."
- (pcase-let ((`((,line-num-1 . ,column-num-1) ,_) (ycmd--get-chunk-bounds c1))
- (`((,line-num-2 . ,column-num-2) ,_) (ycmd--get-chunk-bounds c2)))
- (or (< line-num-1 line-num-2)
- (and (= line-num-1 line-num-2)
- (< column-num-1 column-num-2)))))
-
- (defun ycmd--replace-chunk-list (chunks &optional buffer)
- "Replace list of CHUNKS.
-
- If BUFFER is specified use it as working buffer, else use buffer
- specified in fixit chunk."
- (let ((chunks-sorted (sort chunks 'ycmd--chunk-<))
- (last-line -1)
- (line-delta 0)
- (char-delta 0))
- (dolist (c chunks-sorted)
- (let-alist c
- (pcase-let* ((chunk-bounds (ycmd--get-chunk-bounds c))
- (`((,start-line . ,_) (,end-line . ,_)) chunk-bounds)
- (buffer (or buffer (find-file-noselect
- .range.start.filepath))))
- (unless (= start-line last-line)
- (setq last-line end-line)
- (setq char-delta 0))
- (pcase-let ((`(,new-line-delta . ,new-char-delta)
- (ycmd--replace-chunk chunk-bounds .replacement_text
- line-delta char-delta buffer)))
- (setq line-delta (+ line-delta new-line-delta))
- (setq char-delta (+ char-delta new-char-delta))))))))
-
- (defun ycmd--fixits-have-same-location-p (fixits)
- "Check if mutiple FIXITS have the same location."
- (let ((fixits-by-location
- (--group-by (cdr (assq 'location it)) fixits)))
- (catch 'done
- (dolist (f fixits-by-location)
- (when (> (length (cdr f)) 1)
- (throw 'done t))))))
-
- (defun ycmd--handle-fixit-response (response)
- "Handle a fixit RESPONSE."
- (let ((fixits (cdr (assq 'fixits response))))
- (if (not fixits)
- (message "No fixits found for current line")
- (let ((multiple-fixits-p
- (and (> (length fixits) 1)
- (ycmd--fixits-have-same-location-p fixits))))
- (if (and (not ycmd-confirm-fixit) (not multiple-fixits-p))
- (let ((num-changes-applied 0)
- files-changed)
- (dolist (fixit fixits)
- (-when-let (chunks (cdr (assq 'chunks fixit)))
- (ycmd--loop-chunks-by-filename (chunks)
- (pcase-let ((`(,chunk-path . ,chunk) it))
- (ycmd--replace-chunk-list chunk)
- (cl-incf num-changes-applied (length chunk))
- (unless (member chunk-path files-changed)
- (setq files-changed (append (list chunk-path)
- files-changed)))))))
- (when (> num-changes-applied 0)
- (let* ((num-files-changed (length files-changed))
- (text
- (concat (format "Applied %d changes" num-changes-applied)
- (when (> num-files-changed 1)
- (format " in %d files" num-files-changed)))))
- (message text))))
- (save-current-buffer
- (ycmd--show-fixits
- fixits (and multiple-fixits-p
- (concat
- "Multiple FixIt suggestions are available at this location."
- "Which one would you like to apply?\n")))))))))
-
- (defun ycmd-fixit()
- "Get FixIts for current line."
- (interactive)
- (ycmd-completer "FixIt"))
-
- (defun ycmd-refactor-rename (new-name)
- "Refactor current context with NEW-NAME."
- (interactive "MNew variable name: ")
- (let ((subcommand "RefactorRename"))
- (unless (s-blank-str? new-name)
- (setq subcommand (list subcommand new-name)))
- (ycmd-completer subcommand)))
-
- (defun ycmd-show-documentation (&optional arg)
- "Show documentation for current point in buffer.
-
- If optional ARG is non-nil do not reparse buffer before getting
- the documentation."
- (interactive "P")
- (ycmd-completer (if arg "GetDocImprecise" "GetDoc")))
-
- (defun ycmd--handle-detailed-info-response (response)
- "Handle successful GetDoc RESPONSE."
- (let ((documentation (cdr (assq 'detailed_info response))))
- (if (not (s-blank? documentation))
- (with-help-window (get-buffer-create " *ycmd-documentation*")
- (with-current-buffer standard-output
- (insert documentation)))
- (message "No documentation available for current context"))))
-
- (defmacro ycmd--with-view-buffer (&rest body)
- "Create view buffer and execute BODY in it."
- `(let ((buf (get-buffer-create "*ycmd-locations*")))
- (with-current-buffer buf
- (setq buffer-read-only nil)
- (erase-buffer)
- ,@body
- (goto-char (point-min))
- (ycmd-view-mode)
- buf)))
-
- (defun ycmd--view (response mode)
- "Select `ycmd-view-mode' buffer and display items from RESPONSE.
- MODE is a major mode for fontifaction."
- (let ((view-buffer
- (ycmd--with-view-buffer
- (->>
- (--group-by (cdr (assq 'filepath it)) response)
- (mapc (lambda (it) (ycmd--view-insert-location it mode)))))))
- (pop-to-buffer view-buffer)
- (setq next-error-last-buffer view-buffer)))
-
- (define-button-type 'ycmd--location-button
- 'action #'ycmd--view-jump
- 'face nil)
-
- (defun ycmd--view-jump (button)
- "Jump to BUTTON's location in current window."
- (let ((location (button-get button 'location)))
- (ycmd--goto-location location 'find-file)))
-
- (defun ycmd--view-jump-other-window (button)
- "Jump to BUTTON's location in other window."
- (let ((location (button-get button 'location)))
- (ycmd--goto-location location 'find-file-other-window)))
-
- (defun ycmd--view-insert-button (name location)
- "Insert a view button with NAME and LOCATION."
- (insert-text-button
- name
- 'type 'ycmd--location-button
- 'location location))
-
- (defun ycmd--get-line-from-location (location)
- "Return line from LOCATION."
- (let-alist location
- (--when-let (and .filepath (find-file-noselect .filepath))
- (with-current-buffer it
- (goto-char (ycmd--col-line-to-position
- .column_num .line_num))
- (back-to-indentation)
- (buffer-substring (point) (line-end-position))))))
-
- (defun ycmd--view-insert-location (location-group mode)
- "Insert LOCATION-GROUP into `current-buffer' and fontify according MODE.
- LOCATION-GROUP is a cons cell whose car is the filepath and the whose
- cdr is a list of location objects."
- (pcase-let* ((`(,filepath . ,locations) location-group)
- (max-line-num-width
- (cl-loop for location in locations
- maximize (let ((line-num (cdr (assq 'line_num location))))
- (and line-num (length (format "%d" line-num))))))
- (line-num-format (and max-line-num-width
- (format "%%%dd:" max-line-num-width))))
- (insert (propertize (concat filepath "\n") 'face 'bold))
- (mapc (lambda (it)
- (let-alist it
- (when line-num-format
- (insert (format line-num-format .line_num)))
- (insert " ")
- (let ((description (or (and (not (s-blank? .description))
- (s-trim-left .description))
- (ycmd--get-line-from-location it))))
- (ycmd--view-insert-button
- (ycmd--fontify-code (or description "") mode) it))
- (insert "\n")))
- locations)))
-
- (defvar ycmd-view-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "n") 'next-error-no-select)
- (define-key map (kbd "p") 'previous-error-no-select)
- (define-key map (kbd "q") 'quit-window)
- map))
-
- (define-derived-mode ycmd-view-mode special-mode "ycmd-view"
- "Major mode for locations view and navigation for `ycmd-mode'.
-
- \\{ycmd-view-mode-map}"
- (setq next-error-function #'ycmd--next-location))
-
- (defun ycmd--next-location (num _reset)
- "Navigate to the next location in the view buffer.
- NUM is the number of locations to move forward. If RESET is
- non-nil got to the beginning of buffer before locations
- navigation."
- (forward-button num)
- (ycmd--view-jump-other-window (button-at (point))))
-
- (define-button-type 'ycmd--error-button
- 'face '(error bold underline)
- 'button 't)
-
- (define-button-type 'ycmd--warning-button
- 'face '(warning bold underline)
- 'button 't)
-
- (defun ycmd--make-button (start end type msg)
- "Make a button from START to END of TYPE in the current buffer.
-
- When clicked, MSG will be shown in the minibuffer."
- (make-text-button
- start end
- 'type type
- 'action (lambda (_) (message msg))))
-
- (defconst ycmd--file-ready-buttons
- '(("ERROR" . ycmd--error-button)
- ("WARNING" . ycmd--warning-button))
- "A mapping from parse 'kind' to button types.")
-
- (defun ycmd--line-start-position (line)
- "Find position at the start of LINE."
- (save-excursion
- (ycmd--goto-line line)
- (beginning-of-line)
- (point)))
-
- (defun ycmd--line-end-position (line)
- "Find position at the end of LINE."
- (save-excursion
- (ycmd--goto-line line)
- (end-of-line)
- (point)))
-
- (defun ycmd--decorate-single-parse-result (result)
- "Decorates a buffer based on the contents of a single parse RESULT.
-
- This is a fairly crude form of decoration, but it does give
- reasonable visual feedback on the problems found by ycmd."
- (let-alist result
- (--when-let (find-buffer-visiting .location.filepath)
- (with-current-buffer it
- (let* ((start-pos (ycmd--line-start-position .location.line_num))
- (end-pos (ycmd--line-end-position .location.line_num))
- (btype (cdr (assoc .kind ycmd--file-ready-buttons))))
- (when btype
- (with-silent-modifications
- (ycmd--make-button
- start-pos end-pos
- btype (concat .kind ": " .text
- (when (eq .fixit_available t)
- " (FixIt available)"))))))))))
-
- (defun ycmd-decorate-with-parse-results (results)
- "Decorates a buffer using the RESULTS of a file-ready parse list.
-
- This is suitable as an entry in `ycmd-file-parse-result-hook'."
- (with-silent-modifications
- (set-text-properties (point-min) (point-max) nil))
- (mapc 'ycmd--decorate-single-parse-result results)
- results)
-
- (defun ycmd--display-single-file-parse-result (result)
- "Insert a single file parse RESULT."
- (let-alist result
- (insert (format "%s:%s - %s - %s\n"
- .location.filepath .location.line_num
- .kind .text))))
-
- (defun ycmd-display-file-parse-results (results)
- "Display parse RESULTS in a buffer."
- (let ((buffer "*ycmd-file-parse-results*"))
- (get-buffer-create buffer)
- (with-current-buffer buffer
- (erase-buffer)
- (mapc 'ycmd--display-single-file-parse-result results))
- (display-buffer buffer)))
-
- (defun ycmd-parse-buffer ()
- "Parse buffer."
- (interactive)
- (if (not (ycmd-semantic-completer-available-p))
- (message "Native filetype completion not supported for current file, \
- cannot send parse request")
- (when (ycmd--server-alive-p)
- (let ((buffer (current-buffer)))
- (deferred:$
- (deferred:next
- (lambda ()
- (message "Parsing buffer...")
- (ycmd--reset-parse-status)
- (ycmd--conditional-parse)))
- (deferred:nextc it
- (lambda ()
- (with-current-buffer buffer
- (when (eq ycmd--last-status-change 'parsed)
- (message "Parsing buffer done"))))))))))
-
- (defun ycmd--handle-extra-conf-exception (conf-file)
- "Handle an exception of type `UnknownExtraConf'.
-
- Handle CONF-FILE according the value of `ycmd-extra-conf-handler'."
- (if (not conf-file)
- (warn "No extra_conf_file included in UnknownExtraConf exception. \
- Consider reporting this.")
- (let ((ignore-p (or (eq ycmd-extra-conf-handler 'ignore)
- (not (y-or-n-p (format "Load YCMD extra conf %s? "
- conf-file))))))
- (ycmd--extra-conf-request conf-file ignore-p)
- (ycmd--reset-parse-status)
- (ycmd-notify-file-ready-to-parse))))
-
- (defun ycmd--event-notification (event-name &optional extra-data)
- "Send a event notification for EVENT-NAME.
- Optional EXTRA-DATA contains additional data for the request."
- (let ((content (append (list (cons "event_name" event-name))
- (ycmd--get-basic-request-data)
- extra-data)))
- (deferred:try
- (ycmd--request (make-ycmd-request-data
- :handler "event_notification"
- :content content))
- :catch
- (lambda (err)
- (message "Error sending %s request: %s" event-name err)
- (ycmd--report-status 'errored)
- nil))))
-
- (defun ycmd-notify-file-ready-to-parse ()
- "Send a notification to ycmd that the buffer is ready to be parsed.
-
- Only one active notification is allowed per buffer, and this
- function enforces that constraint.
-
- The response of the notification are passed to all of the
- functions in `ycmd-file-parse-result-hook'."
- (when (and ycmd-mode (not (ycmd-parsing-in-progress-p)))
- (ycmd-with-handled-server-exceptions
- (let ((extra-data
- (append (--when-let (and ycmd-tag-files
- (ycmd--get-tag-files request-buffer))
- (list (cons "tag_files" it)))
- (--when-let (and ycmd-seed-identifiers-with-keywords
- (ycmd--get-keywords request-buffer))
- (list (cons "syntax_keywords" it))))))
- (ycmd--report-status 'parsing)
- (ycmd--event-notification "FileReadyToParse" extra-data))
- :bind-current-buffer t
- :on-exception-form (ycmd--report-status 'errored)
- (ycmd--report-status 'parsed)
- (run-hook-with-args 'ycmd-file-parse-result-hook response))))
-
- (defun ycmd-major-mode-to-file-types (mode)
- "Map a major mode MODE to a list of file-types suitable for ycmd.
-
- If there is no established mapping, return nil."
- (cdr (assq mode ycmd-file-type-map)))
-
- (defun ycmd--on-server-timeout ()
- "Kill server process due to timeout."
- (ycmd-close 'errored)
- (message "ERROR: Ycmd server timeout. If this happens regularly you may need to increase `ycmd-startup-timeout'."))
-
- (defun ycmd--start-server-timeout-timer ()
- "Start the server timeout timer."
- (ycmd--kill-timer ycmd--server-timeout-timer)
- (setq ycmd--server-timeout-timer
- (run-with-timer
- ycmd-startup-timeout nil
- #'ycmd--on-server-timeout)))
-
- (defun ycmd--start-keepalive-timer ()
- "Kill any existing keepalive timer and start a new one."
- (ycmd--kill-timer ycmd--keepalive-timer)
- (setq ycmd--keepalive-timer
- (run-with-timer
- ycmd-keepalive-period
- ycmd-keepalive-period
- #'ycmd--keepalive)))
-
- (defun ycmd--generate-hmac-secret ()
- "Generate a new, random 16-byte HMAC secret key."
- (let ((result '()))
- (dotimes (_ 16 result)
- (setq result (cons (byte-to-string (random 256)) result)))
- (apply 'concat result)))
-
- (defun ycmd--json-encode (obj)
- "Encode a json object OBJ.
- A version of json-encode that uses {} instead of null for nil values.
- This produces output for empty alists that ycmd expects."
- (cl-letf (((symbol-function 'json-encode-keyword)
- (lambda (k) (cond ((eq k t) "true")
- ((eq k json-false) "false")
- ((eq k json-null) "{}")))))
- (json-encode obj)))
-
- ;; This defines 'ycmd--hmac-function which we use to combine an HMAC
- ;; key and message contents.
- (defun ycmd--secure-hash (x)
- "Generate secure sha256 hash of X."
- (secure-hash 'sha256 x nil nil 1))
- (define-hmac-function ycmd--hmac-function
- ycmd--secure-hash 64 64)
-
- (defun ycmd--options-contents (hmac-secret)
- "Return a struct with ycmd options and the HMAC-SECRET applied.
- The struct can be json encoded into a file to create a ycmd
- options file.
-
- When we start a new ycmd server, it needs an options file. It
- reads this file and then deletes it since it contains a secret
- key. So we need to generate a new options file for each ycmd
- instance. This function effectively produces the contents of that
- file."
- (let ((hmac-secret (base64-encode-string hmac-secret))
- (global-config (or ycmd-global-config ""))
- (extra-conf-whitelist (or ycmd-extra-conf-whitelist []))
- (confirm-extra-conf (if (eq ycmd-extra-conf-handler 'load) 0 1))
- (gocode-binary-path (or ycmd-gocode-binary-path ""))
- (godef-binary-path (or ycmd-godef-binary-path ""))
- (rust-src-path (or ycmd-rust-src-path ""))
- (swift-src-path (or ycmd-swift-src-path ""))
- (racerd-binary-path (or ycmd-racerd-binary-path ""))
- (python-binary-path (or ycmd-python-binary-path ""))
- (auto-trigger (if ycmd-auto-trigger-semantic-completion 1 0)))
- `((filepath_completion_use_working_dir . 0)
- (auto_trigger . ,auto-trigger)
- (min_num_of_chars_for_completion . ,ycmd-min-num-chars-for-completion)
- (min_num_identifier_candidate_chars . 0)
- (semantic_triggers . ())
- (filetype_specific_completion_to_disable (gitcommit . 1))
- (collect_identifiers_from_comments_and_strings . 0)
- (max_num_identifier_candidates . ,ycmd-max-num-identifier-candidates)
- (extra_conf_globlist . ,extra-conf-whitelist)
- (global_ycm_extra_conf . ,global-config)
- (confirm_extra_conf . ,confirm-extra-conf)
- (max_diagnostics_to_display . 30)
- (auto_start_csharp_server . 1)
- (auto_stop_csharp_server . 1)
- (use_ultisnips_completer . 1)
- (csharp_server_port . 0)
- (hmac_secret . ,hmac-secret)
- (server_keep_logfiles . 1)
- (gocode_binary_path . ,gocode-binary-path)
- (godef_binary_path . ,godef-binary-path)
- (rust_src_path . ,rust-src-path)
- (swift_src_path . ,swift-src-path)
- (racerd_binary_path . ,racerd-binary-path)
- (python_binary_path . ,python-binary-path))))
-
- (defun ycmd--create-options-file (hmac-secret)
- "Create a new options file for a ycmd server with HMAC-SECRET.
-
- This creates a new tempfile and fills it with options. Returns
- the name of the newly created file."
- (let ((options-file (make-temp-file "ycmd-options"))
- (options (ycmd--options-contents hmac-secret)))
- (with-temp-file options-file
- (insert (ycmd--json-encode options)))
- options-file))
-
- (defun ycmd--exit-code-as-string (code)
- "Return exit status message for CODE."
- (pcase code
- (`3 "unexpected error while loading ycm_core.")
- (`4 (concat "ycm_core library not detected; "
- "you need to compile it by running the "
- "build.py script. See the documentation "
- "for more details."))
- (`5 (concat "ycm_core library compiled for Python 2 "
- "but loaded in Python 3."))
- (`6 (concat "ycm_core library compiled for Python 3 "
- "but loaded in Python 2."))
- (`7 (concat "ycm_core library too old; "
- "PLEASE RECOMPILE by running the build.py "
- "script. See the documentation for more details."))))
-
- (defun ycmd--server-process-sentinel (process event)
- "Handle Ycmd server PROCESS EVENT."
- (when (memq (process-status process) '(exit signal))
- (let* ((code (process-exit-status process))
- (status (if (eq code 0) 'stopped 'errored)))
- (when (eq status 'errored)
- (--if-let (and (eq (process-status process) 'exit)
- (ycmd--exit-code-as-string code))
- (message "Ycmd server error: %s" it)
- (message "Ycmd server %s" (s-replace "\n" "" event))))
- (ycmd--with-all-ycmd-buffers
- (ycmd--report-status status))
- (ycmd--kill-timer ycmd--keepalive-timer))))
-
- (defun ycmd--server-process-filter (process string)
- "Filter function for the Ycmd server PROCESS output STRING."
- ;; insert string into process-buffer
- (when (buffer-live-p (process-buffer process))
- (with-current-buffer (process-buffer process)
- (let ((moving (= (point) (process-mark process))))
- (save-excursion
- (goto-char (process-mark process))
- (let ((inhibit-read-only t))
- (insert-before-markers string))
- (set-marker (process-mark process) (point)))
- (when moving (goto-char (process-mark process))))))
- ;; parse port from server output
- (when (and (not ycmd--server-actual-port)
- (string-match "^serving on http://.*:\\\([0-9]+\\\)$"
- string))
- (ycmd--kill-timer ycmd--server-timeout-timer)
- (setq ycmd--server-actual-port
- (string-to-number (match-string 1 string)))
- (ycmd--with-all-ycmd-buffers
- (ycmd--reset-parse-status))
- (ycmd--perform-deferred-parse)))
-
- (defun ycmd--get-process-environment ()
- "Return `process-evironment'.
- If `ycmd-bypass-url-proxy-services' is non-nil, prepend
- `no_proxy' variable to environment."
- (or ycmd--process-environment
- (setq ycmd--process-environment
- (append (and ycmd-bypass-url-proxy-services
- (not (or (getenv "NO_PROXY")
- (getenv "no_PROXY")
- (getenv "no_proxy")))
- (list (concat "NO_PROXY=" ycmd-host)))
- process-environment))))
-
- (defun ycmd--start-server ()
- "Start a new server and return the process."
- (unless ycmd-server-command
- (user-error "Error: The variable `ycmd-server-command' is not set. \
- See the docstring of the variable for an example"))
- (let ((proc-buff (get-buffer-create ycmd--server-buffer-name)))
- (with-current-buffer proc-buff
- (setq buffer-read-only t)
- (let ((inhibit-read-only t))
- (buffer-disable-undo)
- (erase-buffer)))
- (setq ycmd--process-environment nil)
- (let* ((port (and (numberp ycmd-server-port)
- (> ycmd-server-port 0)
- ycmd-server-port))
- (hmac-secret (ycmd--generate-hmac-secret))
- (options-file (ycmd--create-options-file hmac-secret))
- (args (append (and port (list (format "--port=%d" port)))
- (list (concat "--options_file=" options-file))
- ycmd-server-args))
- (server-program+args (append ycmd-server-command args))
- (process-environment (ycmd--get-process-environment))
- (proc (apply #'start-process ycmd--server-process-name proc-buff
- server-program+args)))
- (ycmd--with-all-ycmd-buffers
- (ycmd--report-status 'starting))
- (setq ycmd--server-actual-port nil
- ycmd--hmac-secret hmac-secret)
- (set-process-query-on-exit-flag proc nil)
- (set-process-sentinel proc #'ycmd--server-process-sentinel)
- (set-process-filter proc #'ycmd--server-process-filter)
- proc)))
-
- (defun ycmd-wait-until-server-is-ready (&optional include-subserver)
- "Wait until server is ready.
- If INCLUDE-SUBSERVER is non-nil wait until subserver is ready.
- Return t when server is ready. Signal error in case of timeout.
- The timeout can be set with the variable
- `ycmd-startup-timeout'."
- (catch 'ready
- (let ((server-start-time (float-time)))
- (ycmd--kill-timer ycmd--server-timeout-timer)
- (while (ycmd-running-p)
- (sit-for 0.1)
- (if (ycmd--server-ready-p include-subserver)
- (progn
- (ycmd--with-all-ycmd-buffers
- (ycmd--reset-parse-status))
- (throw 'ready t))
- ;; timeout after specified period
- (when (< ycmd-startup-timeout
- (- (float-time) server-start-time))
- (ycmd--on-server-timeout)))))))
-
- (defun ycmd--column-in-bytes ()
- "Calculate column offset in bytes for the current position and buffer."
- (- (position-bytes (point))
- (position-bytes (line-beginning-position))))
-
- ;; https://github.com/abingham/emacs-ycmd/issues/165
- (eval-and-compile
- (if (version-list-< (version-to-list emacs-version) '(25))
- (defun ycmd--encode-string (s) s)
- (defun ycmd--encode-string (s) (encode-coding-string s 'utf-8 t))))
-
- (defun ycmd--get-basic-request-data ()
- "Build the basic request data alist for a server request."
- (let* ((column-num (+ 1 (ycmd--column-in-bytes)))
- (line-num (line-number-at-pos (point)))
- (full-path (ycmd--encode-string (or (buffer-file-name) "")))
- (file-contents (ycmd--encode-string
- (buffer-substring-no-properties
- (point-min) (point-max))))
- (file-types (or (ycmd-major-mode-to-file-types major-mode)
- '("generic"))))
- `(("file_data" .
- ((,full-path . (("contents" . ,file-contents)
- ("filetypes" . ,file-types)))))
- ("filepath" . ,full-path)
- ("line_num" . ,line-num)
- ("column_num" . ,column-num))))
-
-
- (defvar ycmd--log-enabled nil
- "If non-nil, http content will be logged.
- This is useful for debugging.")
-
- (defun ycmd-toggle-log-enabled ()
- "Toggle `ycmd--log-enabled' variable."
- (interactive)
- (let ((log-enabled (not ycmd--log-enabled)))
- (message "Ycmd Log %s" (if log-enabled "enabled" "disabled"))
- (setq ycmd--log-enabled log-enabled)))
-
- (defun ycmd--log-content (header content)
- "Insert log with HEADER and CONTENT in a buffer."
- (when ycmd--log-enabled
- (let ((buffer (get-buffer-create "*ycmd-content-log*")))
- (with-current-buffer buffer
- (save-excursion
- (goto-char (point-max))
- (insert (format "\n%s\n\n" header))
- (insert (pp-to-string content)))))))
-
- (defun ycmd-show-debug-info ()
- "Show debug information."
- (interactive)
- (let ((data (make-ycmd-request-data :handler "debug_info"))
- (buffer (current-buffer)))
- (with-help-window (get-buffer-create " *ycmd-debug-info*")
- (with-current-buffer standard-output
- (princ "Ycmd debug information for buffer ")
- (insert (propertize (buffer-name buffer) 'face 'bold))
- (princ " in ")
- (let ((mode (buffer-local-value 'major-mode buffer)))
- (insert-button (symbol-name mode)
- 'type 'help-function
- 'help-args (list mode)))
- (princ ":\n\n")
- (--if-let (and (ycmd--server-alive-p)
- (ycmd-deferred:sync!
- (ycmd-with-handled-server-exceptions
- (ycmd--request data))))
- (pp it)
- (princ "No debug info available from server"))
- (princ "\n\n")
- (princ "Server is ")
- (let ((running (ycmd--server-alive-p)))
- (insert (propertize (if running "running" "not running")
- 'face (if running 'success '(warning bold))))
- (when running
- (insert
- (format " at: %s:%d" ycmd-host ycmd--server-actual-port))))
- (princ "\n\n")
- (princ "Ycmd Mode is ")
- (let ((enabled (buffer-local-value 'ycmd-mode buffer)))
- (insert (propertize (if enabled "enabled" "disabled")
- 'face (if enabled 'success '(warning bold)))))
- (save-excursion
- (let ((end (point)))
- (backward-paragraph)
- (fill-region-as-paragraph (point) end)))
-
- (princ "\n\n--------------------\n\n")
- (princ (format "Ycmd version: %s\n" (ignore-errors (ycmd-version))))
- (princ (format "Emacs version: %s\n" emacs-version))
- (princ (format "System: %s\n" system-configuration))
- (princ (format "Window system: %S\n" window-system))))))
-
- (defun ycmd-filter-and-sort-candidates (request-data)
- "Use ycmd to filter and sort identifiers from REQUEST-DATA.
-
- This request allows to use ycmd's filtering and sorting
- mechanism on arbitrary sets of identifiers.
-
- The request data should be something like:
-
- \((candidates \"candidate1\" \"candidate2\")
- (sort_property . \"\")
- (query . \"cand\"))
-
- If candidates is a list with identifiers, sort_property should be
- and empty string, however when candidates is a more complex
- structure it is used to specify the sort key."
- (let ((data (make-ycmd-request-data
- :handler "filter_and_sort_candidates"
- :content request-data)))
- (ycmd-deferred:sync!
- (ycmd-with-handled-server-exceptions (ycmd--request data)))))
-
- (defun ycmd--send-completer-available-request (&optional mode)
- "Send request to check if a semantic completer exists for MODE.
- Response is non-nil if semantic complettion is available."
- (let ((data (make-ycmd-request-data
- :handler "semantic_completion_available")))
- (when mode
- (let* ((buffer (current-buffer))
- (full-path (ycmd--encode-string (or (buffer-file-name buffer) "")))
- (content (ycmd-request-data-content data))
- (file-types (assoc "filetypes"
- (assoc full-path
- (assoc "file_data" content)))))
- (when (consp file-types)
- (setcdr file-types (ycmd-major-mode-to-file-types mode)))))
- (ycmd-deferred:sync!
- (ycmd-with-handled-server-exceptions (ycmd--request data)))))
-
- (defun ycmd-semantic-completer-available-p ()
- "Return t if a semantic completer is available for current `major-mode'."
- (let ((mode major-mode))
- (or (gethash mode ycmd--available-completers)
- (--when-let (ycmd--send-completer-available-request mode)
- (puthash mode (or (eq it t) 'none) ycmd--available-completers)))))
-
- (defun ycmd--get-request-hmac (method path body)
- "Generate HMAC for request from METHOD, PATH and BODY."
- (ycmd--hmac-function
- (mapconcat (lambda (val)
- (ycmd--hmac-function
- (ycmd--encode-string val) ycmd--hmac-secret))
- `(,method ,path ,(or body "")) "")
- ycmd--hmac-secret))
-
- (cl-defstruct ycmd-request-data
- "Structure for storing the ycmd server request data.
-
- Slots:
-
- `handler'
- Specifies the the path portion of the URL. For example, if
- HANDLER is 'feed_llama', the request URL is
- 'http://host:port/feed_llama'.
-
- `content'
- An alist that will be JSON-encoded and sent over at the
- content of the HTTP message."
- handler
- (content (ycmd--get-basic-request-data)))
-
- (cl-defun ycmd--request (request-data
- &key
- (type "POST")
- (params nil))
- "Send an asynchronous HTTP request to the ycmd server.
-
- This starts the server if necessary.
-
- Returns a deferred object which resolves to the content of the
- response message.
-
- REQUEST-DATA is a `ycmd-request-data' structure.
-
- PARSER specifies the function that will be used to parse the
- response to the message. Typical values are buffer-string and
- json-read. This function will be passed an the completely
- unmodified contents of the response (i.e. not JSON-decoded or
- anything like that)."
- (unless (ycmd--server-alive-p)
- (message "Ycmd server is not running. Can't send `%s' request!"
- (ycmd-request-data-handler request-data))
- (cl-return-from ycmd--request (deferred:next)))
-
- (let* ((url-show-status (not ycmd-hide-url-status))
- (url-proxy-services (unless ycmd-bypass-url-proxy-services
- url-proxy-services))
- (process-environment (ycmd--get-process-environment))
- (path (concat "/" (ycmd-request-data-handler request-data)))
- (content (json-encode (ycmd-request-data-content request-data)))
- (hmac (ycmd--get-request-hmac type path content))
- (encoded-hmac (base64-encode-string hmac 't))
- (url (format "http://%s:%s%s"
- ycmd-host ycmd--server-actual-port path))
- (headers `(("Content-Type" . "application/json")
- ("X-Ycm-Hmac" . ,encoded-hmac)))
- (parser (lambda ()
- (let ((json-array-type 'list))
- (json-read)))))
- (ycmd--log-content "HTTP REQUEST CONTENT" content)
-
- (deferred:$
- (request-deferred url :type type :params params :data content
- :parser parser :headers headers)
- (deferred:nextc it
- (lambda (response)
- (let ((data (request-response-data response)))
- (ycmd--log-content "HTTP RESPONSE CONTENT" data)
- data))))))
-
- (provide 'ycmd)
-
- ;;; ycmd.el ends here
-
- ;; Local Variables:
- ;; indent-tabs-mode: nil
- ;; End:
|