;;; ccls.el --- ccls client for lsp-mode     -*- lexical-binding: t; -*-

;; Copyright (C) 2017 Tobias Pisani
;; Copyright (C) 2018 Fangrui Song
;; Copyright (C) 2020-2025  emacs-lsp maintainers

;; Author: Tobias Pisani, Fangrui Song
;; Package-Version: 20260507.1746
;; Package-Revision: f728c92e3384
;; Homepage: https://github.com/emacs-lsp/emacs-ccls
;; Package-Requires: ((emacs "29.1") (lsp-mode "6.3.1") (dash "2.14.1"))
;; Keywords: languages, lsp, c++

;; 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.
;;
;;; Commentary:

;; To enable, call (lsp) in c-mode-hook c++-mode-hook objc-mode-hook.
;; See more at https://github.com/MaskRay/ccls/wiki/Emacs

;;; Code:

(require 'ccls-common)
(require 'ccls-semantic-highlight)
(require 'ccls-code-lens)
(require 'ccls-tree)
(require 'ccls-call-hierarchy)
(require 'ccls-inheritance-hierarchy)
(require 'ccls-member-hierarchy)

(require 'seq)

;; ---------------------------------------------------------------------
;;   Customization
;; ---------------------------------------------------------------------

(defcustom ccls-executable
  "ccls"
  "Path of the ccls executable."
  :type 'file
  :group 'ccls)

(defcustom ccls-args
  nil
  "Additional command line options passed to the ccls executable."
  :type '(repeat string)
  :group 'ccls)

(defcustom ccls-library-folders-fn
  nil
  "Function which returns a list of library folders."
  :type 'function
  :group 'ccls)

(defcustom ccls-initialization-options
  nil
  "initializationOptions"
  :group 'ccls
  :type 'alist)
(put 'ccls-initialization-options 'safe-local-variable 'listp)

(defcustom ccls-root-files
  '(".ccls-root")
  "A list of files considered to mark the root of a ccls project."
  :type '(repeat string)
  :group 'ccls)

;; ---------------------------------------------------------------------
;;   Other ccls-specific methods
;; ---------------------------------------------------------------------
;;

(eval-and-compile
  (lsp-interface
   (CclsQueryFileDef (:path :args :language :dependencies :includes :skipped-ranges) nil)))

(defun ccls-info ()
  (lsp-request "$ccls/info" (make-hash-table)))

(defun ccls-file-info (&optional extra)
  (lsp-request "$ccls/fileInfo"
               (append (lsp--text-document-position-params) extra)))

(defun ccls-preprocess-file (&optional output-buffer)
  "Preprocess selected buffer."
  (interactive)
  (lsp--cur-workspace-check)
  (-when-let* ((mode major-mode)
               (info (ccls-file-info))
               (args (seq-into (lsp:ccls-query-file-def-args info) 'vector))
               (new-args (let ((i 0) ret)
                           (while (< i (length args))
                             (let ((arg (elt args i)))
                               (cond
                                ((string= arg "-o") (cl-incf i))
                                ((string-match-p "\\`-o.+" arg))
                                (t (push arg ret))))
                             (cl-incf i))
                           (nreverse ret))))
    (with-current-buffer (or output-buffer
                             (get-buffer-create
                              (format "*lsp-ccls preprocess %s*" (buffer-name))))
      (pop-to-buffer (current-buffer))
      (with-silent-modifications
        (erase-buffer)
        (insert (format "// Generated by: %s"
                        (combine-and-quote-strings new-args)))
        (insert (with-output-to-string
                  (with-current-buffer standard-output
                    (apply #'process-file (car new-args) nil t nil "-E" (cdr new-args)))))
        (delay-mode-hooks (funcall mode))
        (setq buffer-read-only t)))))

(defun ccls-reload ()
  "Reset database and reload cached index files."
  (interactive)
  (lsp-notify "$ccls/reload" (list :whitelist [] :blacklist [])))

(defun ccls-navigate (direction)
  "Navigate to a nearby outline symbol.
DIRECTION can be \"D\", \"L\", \"R\" or \"U\"."
  (lsp-find-custom "$ccls/navigate" `(:direction ,direction)))

;; ---------------------------------------------------------------------
;;  Register lsp client
;; ---------------------------------------------------------------------

(defun ccls--suggest-project-root ()
  (and (memq major-mode '(c-mode c++-mode cuda-mode objc-mode))
       (when-let (dir (cl-some  #'(lambda (file) (locate-dominating-file default-directory file))
                                ccls-root-files))
         (expand-file-name dir))))

(cl-defmethod lsp-execute-command
  ((_server (eql ccls)) (command (eql ccls.xref)) arguments)
  (when-let ((xrefs (lsp--locations-to-xref-items
                     (lsp--send-execute-command "ccls.xref" arguments))))
    (lsp-show-xrefs xrefs nil t)))

(advice-add 'lsp--suggest-project-root :before-until #'ccls--suggest-project-root)

(lsp-register-client
 (make-lsp-client
  :new-connection (lsp-stdio-connection (lambda () (cons ccls-executable ccls-args)))
  :activation-fn (lsp-activate-on "c" "cpp" "objective-c" "cuda")
  :server-id 'ccls
  :multi-root nil
  :notification-handlers
  (lsp-ht ("$ccls/publishSkippedRanges" #'ccls--publish-skipped-ranges)
          ("$ccls/publishSemanticHighlight" #'ccls--publish-semantic-highlight))
  :initialization-options (lambda () ccls-initialization-options)
  :library-folders-fn (lambda (&rest args)
                        (if ccls-library-folders-fn (apply ccls-library-folders-fn args)))))

(provide 'ccls)
;;; ccls.el ends here
