├── .github └── workflows │ └── test.yml ├── .gitignore ├── Eask ├── Makefile ├── README.md ├── ccls-call-hierarchy.el ├── ccls-code-lens.el ├── ccls-common.el ├── ccls-inheritance-hierarchy.el ├── ccls-member-hierarchy.el ├── ccls-semantic-highlight.el ├── ccls-tree.el ├── ccls.el └── test /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | continue-on-error: ${{ matrix.experimental }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu-latest, macos-latest, windows-latest] 19 | emacs-version: 20 | - 28.2 21 | - 29.4 22 | - 30.1 23 | experimental: [false] 24 | include: 25 | - os: ubuntu-latest 26 | emacs-version: snapshot 27 | experimental: true 28 | - os: macos-latest 29 | emacs-version: snapshot 30 | experimental: true 31 | - os: windows-latest 32 | emacs-version: snapshot 33 | experimental: true 34 | 35 | steps: 36 | - uses: actions/checkout@v3 37 | 38 | - uses: jcs090218/setup-emacs@master 39 | with: 40 | version: ${{ matrix.emacs-version }} 41 | 42 | - uses: emacs-eask/setup-eask@master 43 | with: 44 | version: 'snapshot' 45 | 46 | - name: Run tests 47 | run: 'make ci' 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eask 2 | /dist 3 | *.elc 4 | *autoloads* 5 | -------------------------------------------------------------------------------- /Eask: -------------------------------------------------------------------------------- 1 | ;; -*- mode: eask; lexical-binding: t -*- 2 | 3 | (package "ccls" 4 | "0.1" 5 | "ccls client for lsp-mode") 6 | 7 | (website-url "https://github.com/emacs-lsp/emacs-ccls") 8 | (keywords "languages" "lsp" "c++") 9 | 10 | (package-file "ccls.el") 11 | 12 | (files "*.el") 13 | 14 | (script "test" "echo \"Error: no test specified\" && exit 1") 15 | 16 | (source "gnu") 17 | (source "melpa") 18 | 19 | (depends-on "emacs" "28.1") 20 | (depends-on "lsp-mode") 21 | (depends-on "dash") 22 | 23 | (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/usr/bin/env bash 2 | 3 | EMACS ?= emacs 4 | EASK ?= eask 5 | 6 | ci: build compile checkdoc lint 7 | 8 | build: 9 | $(EASK) package 10 | $(EASK) install 11 | 12 | compile: 13 | @echo "Compiling..." 14 | $(EASK) compile 15 | 16 | checkdoc: 17 | $(EASK) lint checkdoc 18 | 19 | lint: 20 | @echo "package linting..." 21 | $(EASK) lint package 22 | 23 | clean: 24 | $(EASK) clean all 25 | 26 | .PHONY : test compile checkdoc lint clean tag 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](https://melpa.org/packages/ccls-badge.svg)](https://melpa.org/#/ccls) 2 | 3 | # emacs-ccls 4 | 5 | emacs-ccls is a client for [ccls](https://github.com/MaskRay/ccls), a C/C++/Objective-C language server supporting multi-million line C++ code-bases, powered by libclang. 6 | 7 | It leverages [lsp-mode](https://github.com/emacs-lsp/lsp-mode), but also provides some ccls extensions to LSP: 8 | 9 | * semantic highlighting 10 | * skipped ranges (e.g. a `#if false` region) 11 | * cross references: `$ccls/inheritance` `$ccls/call` `$ccls/vars` 12 | 13 | ## Quickstart 14 | 15 | ```elisp 16 | (require 'ccls) 17 | (setq ccls-executable "/path/to/ccls/Release/ccls") 18 | ``` 19 | 20 | Refer to for details. 21 | 22 | 23 | 24 | 25 | 26 | 27 | `$ccls/call` (caller/callee, with hierarchical view) 28 | 29 | ### `ccls-navigate` 30 | 31 | https://github.com/MaskRay/ccls/wiki/lsp-mode#ccls-navigate 32 | 33 | ![ccls-navigate](https://raw.githubusercontent.com/MaskRay/ccls-static/master/emacs-ccls/ccls-navigate.gif) 34 | 35 | ## License 36 | 37 | [MIT](http://opensource.org/licenses/MIT) 38 | -------------------------------------------------------------------------------- /ccls-call-hierarchy.el: -------------------------------------------------------------------------------- 1 | ;;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Tobias Pisani 4 | ;; Copyright (C) 2018-2020 Fangrui Song 5 | 6 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 7 | ;; of this software and associated documentation files (the "Software"), to deal 8 | ;; in the Software without restriction, including without limitation the rights 9 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and-or sell 10 | ;; copies of the Software, and to permit persons to whom the Software is 11 | ;; furnished to do so, subject to the following conditions: 12 | 13 | ;; The above copyright notice and this permission notice shall be included in 14 | ;; all copies or substantial portions of the Software. 15 | 16 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | ;; SOFTWARE. 23 | 24 | ;;; Code: 25 | 26 | (require 'ccls-common) 27 | (require 'ccls-tree) 28 | 29 | ;; --------------------------------------------------------------------- 30 | ;; Customization 31 | ;; --------------------------------------------------------------------- 32 | 33 | (defface ccls-call-hierarchy-node-normal-face 34 | nil 35 | "." 36 | :group 'ccls) 37 | 38 | (defface ccls-call-hierarchy-node-base-face 39 | '((t (:foreground "orange red"))) 40 | "." 41 | :group 'ccls) 42 | 43 | (defface ccls-call-hierarchy-node-derived-face 44 | '((t (:foreground "orange"))) 45 | "." 46 | :group 'ccls) 47 | 48 | (defcustom ccls-call-hierarchy-qualified t 49 | "Use qualified name for call hierarchy." 50 | :group 'ccls 51 | :type 'boolean) 52 | 53 | ;; --------------------------------------------------------------------- 54 | ;; Tree node 55 | ;; --------------------------------------------------------------------- 56 | 57 | (cl-defstruct ccls-call-hierarchy-node 58 | id 59 | name 60 | call-type) 61 | 62 | (eval-and-compile 63 | (lsp-interface 64 | (CclsCall (:id :name :location :callType :numChildren :children) nil))) 65 | 66 | (defun ccls-call-hierarchy--read-node (data &optional parent) 67 | "Construct a call tree node from hashmap DATA and give it the parent PARENT." 68 | (-let* (((&CclsCall :id :name :location :call-type :num-children) data) 69 | (filename (lsp--uri-to-path (lsp:location-uri location)))) 70 | (make-ccls-tree-node 71 | :location (cons filename (lsp:range-start (lsp:location-range location))) 72 | :has-children (< 0 num-children) 73 | :parent parent 74 | :expanded nil 75 | :children nil 76 | :data (make-ccls-call-hierarchy-node 77 | :id id 78 | :name name 79 | :call-type call-type)))) 80 | 81 | (defun ccls-call-hierarchy--request-children (callee node) 82 | "." 83 | (let ((id (ccls-call-hierarchy-node-id (ccls-tree-node-data node)))) 84 | (--map (ccls-call-hierarchy--read-node it node) 85 | (lsp:ccls-call-children 86 | (lsp-request 87 | "$ccls/call" 88 | `(:id ,id 89 | :callee ,callee 90 | :callType 3 91 | :levels ,ccls-tree-initial-levels 92 | :qualified ,(if ccls-call-hierarchy-qualified t :json-false) 93 | :hierarchy t)))))) 94 | 95 | (defun ccls-call-hierarchy--request-init (callee) 96 | "." 97 | (lsp-request 98 | "$ccls/call" 99 | `(:textDocument (:uri ,(concat lsp--uri-file-prefix buffer-file-name)) 100 | :position ,(lsp--cur-position) 101 | :callee ,callee 102 | :callType 3 103 | :qualified ,(if ccls-call-hierarchy-qualified t :json-false) 104 | :hierarchy t))) 105 | 106 | (defun ccls-call-hierarchy--make-string (node depth) 107 | "Propertize the name of NODE with the correct properties" 108 | (let ((data (ccls-tree-node-data node))) 109 | (if (= depth 0) 110 | (ccls-call-hierarchy-node-name data) 111 | (concat 112 | (propertize (ccls-call-hierarchy-node-name data) 113 | 'face (pcase (ccls-call-hierarchy-node-call-type data) 114 | ('0 'ccls-call-hierarchy-node-normal-face) 115 | ('1 'ccls-call-hierarchy-node-base-face) 116 | ('2 'ccls-call-hierarchy-node-derived-face))) 117 | (propertize (format " (%s:%s)" 118 | (file-name-nondirectory (car (ccls-tree-node-location node))) 119 | (lsp:position-line (cdr (ccls-tree-node-location node)))) 120 | 'face 'ccls-tree-mode-line-face))))) 121 | 122 | (defun ccls-call-hierarchy (callee) 123 | (interactive "P") 124 | (setq callee (if callee t :json-false)) 125 | (ccls-tree--open 126 | (make-ccls-tree-client 127 | :name "call hierarchy" 128 | :mode-line-format (format " %s %s %s %s" 129 | (propertize (if (eq callee t) "Callee types:" "Caller types:") 'face 'ccls-tree-mode-line-face) 130 | (propertize "Normal" 'face 'ccls-call-hierarchy-node-normal-face) 131 | (propertize "Base" 'face 'ccls-call-hierarchy-node-base-face) 132 | (propertize "Derived" 'face 'ccls-call-hierarchy-node-derived-face)) 133 | :top-line-f (lambda () (propertize (if (eq callee t) "Callees of " "Callers of") 'face 'ccls-tree-mode-line-face)) 134 | :make-string-f 'ccls-call-hierarchy--make-string 135 | :read-node-f 'ccls-call-hierarchy--read-node 136 | :request-children-f (apply-partially #'ccls-call-hierarchy--request-children callee) 137 | :request-init-f (lambda () (ccls-call-hierarchy--request-init callee))))) 138 | 139 | (provide 'ccls-call-hierarchy) 140 | ;;; ccls-call-hierarchy.el ends here 141 | -------------------------------------------------------------------------------- /ccls-code-lens.el: -------------------------------------------------------------------------------- 1 | ;;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Tobias Pisani 4 | ;; Copyright (C) 2018-2020 Fangrui Song 5 | 6 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 7 | ;; of this software and associated documentation files (the "Software"), to deal 8 | ;; in the Software without restriction, including without limitation the rights 9 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and-or sell 10 | ;; copies of the Software, and to permit persons to whom the Software is 11 | ;; furnished to do so, subject to the following conditions: 12 | 13 | ;; The above copyright notice and this permission notice shall be included in 14 | ;; all copies or substantial portions of the Software. 15 | 16 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | ;; SOFTWARE. 23 | 24 | ;;; Code: 25 | 26 | (require 'ccls-common) 27 | 28 | (defgroup ccls-code-lens nil 29 | "ccls code lens." 30 | :group 'tools 31 | :group 'ccls) 32 | 33 | (defcustom ccls-code-lens-position 'end 34 | "The position to put code lens overlays." 35 | :type '(choice (const end) (const inplace)) 36 | :group 'ccls-code-lens) 37 | 38 | (defface ccls-code-lens-face 39 | '((t :inherit shadow)) 40 | "The face used for code lens overlays." 41 | :group 'ccls-code-lens) 42 | 43 | (defface ccls-code-lens-mouse-face 44 | '((t :box t)) 45 | "The face used for code lens overlays." 46 | :group 'ccls-code-lens) 47 | 48 | ;; --------------------------------------------------------------------- 49 | ;; Codelens 50 | ;; 51 | ;; Enable by calling `ccls-request-code-lens' 52 | ;; Clear them away using `ccls-clear-code-lens' 53 | ;; 54 | ;; TODO: 55 | ;; - Find a better way to display them. 56 | ;; 57 | ;; - Instead of adding multiple lenses to one symbol, append the text 58 | ;; of the new one to the old. This will fix flickering when moving 59 | ;; over lenses. 60 | ;; 61 | ;; - Add a global option to request code lenses on automatically 62 | ;; --------------------------------------------------------------------- 63 | 64 | (defun ccls--make-code-lens-string (lpad command0 &optional rpad) 65 | "." 66 | (-let ((map (make-sparse-keymap)) 67 | ((&Command :title :command :arguments?) command0)) 68 | (define-key map [mouse-1] 69 | (lambda () (interactive) 70 | (when-let ((xrefs (lsp--locations-to-xref-items 71 | (lsp--send-execute-command command arguments?)))) 72 | (lsp-show-xrefs xrefs nil t)))) 73 | (propertize (concat lpad title rpad) 74 | 'face 'ccls-code-lens-face 75 | 'mouse-face 'ccls-code-lens-mouse-face 76 | 'local-map map))) 77 | 78 | (defun ccls--code-lens-callback (result) 79 | "." 80 | (overlay-recenter (point-max)) 81 | (ccls-clear-code-lens) 82 | (setq 83 | result 84 | (seq-sort 85 | (lambda (x y) 86 | (let ((xl (aref x 2)) (yl (aref y 2))) 87 | (if (/= xl yl) (< xl yl) (< (aref x 3) (aref y 3))))) 88 | (seq-map (lambda (lens) 89 | (-let* (((&CodeLens :command? :range) lens) 90 | ((&Range :start :end) range)) 91 | (vector (lsp:position-line start) (lsp:position-character start) 92 | (lsp:position-line end) (lsp:position-character end) command?) 93 | )) result))) 94 | (save-excursion 95 | (widen) 96 | (goto-char 1) 97 | (let ((line 0) (col 0) ov) 98 | (seq-doseq (lens result) 99 | (-let (([l0 c0 l1 c1 command?] lens)) 100 | (pcase ccls-code-lens-position 101 | ('end 102 | (forward-line (- l0 line)) 103 | (if (and ov (= l0 line)) 104 | (overlay-put ov 'display 105 | (concat (overlay-get ov 'display) 106 | (ccls--make-code-lens-string (if (/= c0 col) "|" " ") command?))) 107 | (when ov 108 | (overlay-put ov 'display (concat (overlay-get ov 'display) "\n"))) 109 | (let ((p (line-end-position))) 110 | (setq ov (make-overlay p (1+ p) nil 'front-advance)) 111 | (overlay-put ov 'ccls-code-lens t) 112 | (overlay-put ov 'display (ccls--make-code-lens-string " " command?)))) 113 | (setq line l0 col c0)) 114 | ('inplace 115 | (forward-line (- l1 line)) 116 | (forward-char c1) 117 | (setq line l1) 118 | (setq ov (make-overlay (point) (point))) 119 | (overlay-put ov 'ccls-code-lens t) 120 | (overlay-put ov 'after-string (ccls--make-code-lens-string " " command?))))) 121 | ) 122 | (when (and (eq ccls-code-lens-position 'end) ov) 123 | (overlay-put ov 'display (concat (overlay-get ov 'display) "\n")))))) 124 | 125 | (defun ccls-request-code-lens (&optional buffer) 126 | "Request code lens from ccls." 127 | (interactive) 128 | (with-current-buffer (or buffer (current-buffer)) 129 | (lsp--cur-workspace-check) 130 | (lsp--send-request-async 131 | (lsp--make-request "textDocument/codeLens" 132 | `(:textDocument (:uri ,(concat lsp--uri-file-prefix buffer-file-name)))) 133 | #'ccls--code-lens-callback))) 134 | 135 | (defun ccls-clear-code-lens () 136 | "Clear all code lenses from this buffer." 137 | (interactive) 138 | (remove-overlays nil nil 'ccls-code-lens t)) 139 | 140 | (defun ccls-code-lens--request-when-idle () 141 | (run-with-idle-timer 0.5 nil #'ccls-request-code-lens (current-buffer))) 142 | 143 | (define-minor-mode ccls-code-lens-mode 144 | "toggle code-lens overlays" 145 | :group 'ccls 146 | :global nil 147 | :init-value nil 148 | :lighter "Lens" 149 | (cond 150 | (ccls-code-lens-mode 151 | (when (lsp-workspaces) 152 | (ccls-request-code-lens) 153 | (add-hook 'lsp-diagnostics-updated-hook 'ccls-code-lens--request-when-idle t t))) 154 | (t 155 | (remove-hook 'lsp-diagnostics-updated-hook 'ccls-code-lens--request-when-idle t) 156 | (ccls-clear-code-lens)))) 157 | 158 | (provide 'ccls-code-lens) 159 | -------------------------------------------------------------------------------- /ccls-common.el: -------------------------------------------------------------------------------- 1 | ;;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Tobias Pisani 4 | ;; Copyright (C) 2018 Fangrui Song 5 | 6 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 7 | ;; of this software and associated documentation files (the "Software"), to deal 8 | ;; in the Software without restriction, including without limitation the rights 9 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and-or sell 10 | ;; copies of the Software, and to permit persons to whom the Software is 11 | ;; furnished to do so, subject to the following conditions: 12 | 13 | ;; The above copyright notice and this permission notice shall be included in 14 | ;; all copies or substantial portions of the Software. 15 | 16 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | ;; SOFTWARE. 23 | 24 | ;;; Code: 25 | 26 | (require 'cc-mode) 27 | (require 'lsp-mode) 28 | (require 'cl-lib) 29 | (require 'seq) 30 | (require 'subr-x) 31 | (require 'dash) 32 | 33 | (defgroup ccls nil 34 | "Customization options for the ccls client" 35 | :group 'tools) 36 | 37 | (provide 'ccls-common) 38 | -------------------------------------------------------------------------------- /ccls-inheritance-hierarchy.el: -------------------------------------------------------------------------------- 1 | ;;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Tobias Pisani 4 | ;; Copyright (C) 2018-2020 Fangrui Song 5 | 6 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 7 | ;; of this software and associated documentation files (the "Software"), to deal 8 | ;; in the Software without restriction, including without limitation the rights 9 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and-or sell 10 | ;; copies of the Software, and to permit persons to whom the Software is 11 | ;; furnished to do so, subject to the following conditions: 12 | 13 | ;; The above copyright notice and this permission notice shall be included in 14 | ;; all copies or substantial portions of the Software. 15 | 16 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | ;; SOFTWARE. 23 | 24 | ;;; Code: 25 | 26 | (require 'ccls-common) 27 | (require 'ccls-tree) 28 | 29 | 30 | (defface ccls-inheritance-hierarchy-base-face 31 | '((t (:foreground "orange red"))) 32 | "." 33 | :group 'ccls) 34 | 35 | (defcustom ccls-inheritance-hierarchy-qualified t 36 | "Use qualified name for types in inheritance hierarchy." 37 | :group 'ccls 38 | :type 'boolean) 39 | 40 | (cl-defstruct ccls-inheritance-hierarchy-node 41 | id 42 | kind 43 | name) 44 | 45 | (eval-and-compile 46 | (lsp-interface 47 | (CclsInheritance (:id :kind :name :location :numChildren :children) nil))) 48 | 49 | (defun ccls-inheritance-hierarchy--read-node (data &optional parent) 50 | "Construct a call tree node from hashmap DATA and give it the parent PARENT" 51 | (-let* (((&CclsInheritance :id :kind :name :location :num-children :children) data) 52 | (filename (lsp--uri-to-path (lsp:location-uri location))) 53 | (node 54 | (make-ccls-tree-node 55 | :location (cons filename (lsp:range-start (lsp:location-range location))) 56 | :has-children (< 0 num-children) 57 | :parent parent 58 | :expanded nil 59 | :children nil 60 | :data (make-ccls-inheritance-hierarchy-node 61 | :id id 62 | :kind kind 63 | :name name)))) 64 | (setf (ccls-tree-node-children node) 65 | (--map (ccls-inheritance-hierarchy--read-node it node) 66 | (lsp:ccls-inheritance-children data))) 67 | node)) 68 | 69 | (defun ccls-inheritance-hierarchy--request-children (derived node) 70 | "." 71 | (let ((id (ccls-inheritance-hierarchy-node-id (ccls-tree-node-data node))) 72 | (kind (ccls-inheritance-hierarchy-node-kind (ccls-tree-node-data node)))) 73 | (--map (ccls-inheritance-hierarchy--read-node it node) 74 | (lsp:ccls-inheritance-children 75 | (lsp-request 76 | "$ccls/inheritance" 77 | `(:id ,id :kind ,kind 78 | :derived ,derived 79 | :qualified ,(if ccls-inheritance-hierarchy-qualified t :json-false) 80 | :levels ,ccls-tree-initial-levels 81 | :hierarchy t 82 | )))))) 83 | 84 | (defun ccls-inheritance-hierarchy--request-init (derived) 85 | "." 86 | (lsp-request 87 | "$ccls/inheritance" 88 | `(:textDocument (:uri ,(concat lsp--uri-file-prefix buffer-file-name)) 89 | :position ,(lsp--cur-position) 90 | 91 | :derived ,derived 92 | :qualified ,(if ccls-inheritance-hierarchy-qualified t :json-false) 93 | :levels 1 94 | :hierarchy t))) 95 | 96 | (defun ccls-inheritance-hierarchy--make-string (node _depth) 97 | "Propertize the name of NODE with the correct properties" 98 | (let* ((data (ccls-tree-node-data node)) 99 | (name (ccls-inheritance-hierarchy-node-name data))) 100 | (if (string-equal name "[[Base]]") 101 | (propertize "Bases" 'face 'ccls-inheritance-hierarchy-base-face) 102 | name))) 103 | 104 | (defun ccls-inheritance-hierarchy (derived) 105 | (interactive "P") 106 | (let ((json-derived (if derived t :json-false))) 107 | (ccls-tree--open 108 | (make-ccls-tree-client 109 | :name "inheritance hierarchy" 110 | :mode-line-format (propertize (if derived 111 | "Inheritance Hierarchy: Subclasses" 112 | "Inheritance Hierarchy: Bases") 113 | 'face 'ccls-tree-mode-line-face) 114 | :top-line-f (lambda () (propertize (if derived "Derive from" "Bases of") 'face 'ccls-tree-mode-line-face)) 115 | :make-string-f 'ccls-inheritance-hierarchy--make-string 116 | :read-node-f 'ccls-inheritance-hierarchy--read-node 117 | :request-children-f (apply-partially #'ccls-inheritance-hierarchy--request-children json-derived) 118 | :request-init-f (lambda () (ccls-inheritance-hierarchy--request-init json-derived)))))) 119 | 120 | (provide 'ccls-inheritance-hierarchy) 121 | ;;; ccls-inheritance-hierarchy.el ends here 122 | -------------------------------------------------------------------------------- /ccls-member-hierarchy.el: -------------------------------------------------------------------------------- 1 | ;;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Tobias Pisani 4 | ;; Copyright (C) 2018-2020 Fangrui Song 5 | 6 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 7 | ;; of this software and associated documentation files (the "Software"), to deal 8 | ;; in the Software without restriction, including without limitation the rights 9 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and-or sell 10 | ;; copies of the Software, and to permit persons to whom the Software is 11 | ;; furnished to do so, subject to the following conditions: 12 | 13 | ;; The above copyright notice and this permission notice shall be included in 14 | ;; all copies or substantial portions of the Software. 15 | 16 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | ;; SOFTWARE. 23 | 24 | ;;; Code: 25 | 26 | (require 'ccls-common) 27 | (require 'ccls-tree) 28 | 29 | (defcustom ccls-member-hierarchy-qualified nil 30 | "Use detailed name for member hierarchy" 31 | :group 'ccls 32 | :type 'boolean) 33 | 34 | ;; --------------------------------------------------------------------- 35 | ;; Tree node 36 | ;; --------------------------------------------------------------------- 37 | 38 | (cl-defstruct ccls-member-hierarchy-node 39 | name 40 | field-name 41 | id) 42 | 43 | (eval-and-compile 44 | (lsp-interface 45 | (CclsMember (:id :name :fieldName :location :numChildren :children) nil))) 46 | 47 | (defun ccls-member-hierarchy--read-node (data &optional parent) 48 | "Construct a call tree node from hashmap DATA and give it the parent PARENT" 49 | (-let* (((&CclsMember :id :name :field-name :location :num-children :children) data) 50 | (filename (lsp--uri-to-path (lsp:location-uri location))) 51 | (node 52 | (make-ccls-tree-node 53 | :location (cons filename (lsp:range-start (lsp:location-range location))) 54 | ;; With a little bit of luck, this only filters out enums 55 | :has-children (not (or (>= 0 num-children) 56 | (null parent) 57 | (equal id (ccls-member-hierarchy-node-id (ccls-tree-node-data parent))))) 58 | :parent parent 59 | :expanded nil 60 | :children nil 61 | :data (make-ccls-member-hierarchy-node 62 | :name name 63 | :field-name field-name 64 | :id id)))) 65 | (setf (ccls-tree-node-children node) 66 | (--map (ccls-member-hierarchy--read-node it node) 67 | children)) 68 | node)) 69 | 70 | (defun ccls-member-hierarchy--request-children (node) 71 | "." 72 | (let ((id (ccls-member-hierarchy-node-id (ccls-tree-node-data node)))) 73 | (--map (ccls-member-hierarchy--read-node it node) 74 | (lsp:ccls-member-children 75 | (lsp-request "$ccls/member" 76 | `(:id ,id 77 | :levels ,ccls-tree-initial-levels 78 | :qualified ,(if ccls-member-hierarchy-qualified t :json-false) 79 | :hierarchy t 80 | )))))) 81 | 82 | (defun ccls-member-hierarchy--request-init () 83 | "." 84 | (lsp-request 85 | "$ccls/member" 86 | `(:textDocument (:uri ,(concat lsp--uri-file-prefix buffer-file-name)) 87 | :position ,(lsp--cur-position) 88 | :levels 1 89 | :qualified ,(if ccls-member-hierarchy-qualified t :json-false) 90 | :hierarchy t))) 91 | 92 | (defun ccls-member-hierarchy--make-string (node depth) 93 | "Propertize the name of NODE with the correct properties" 94 | (let ((data (ccls-tree-node-data node))) 95 | (if (= depth 0) 96 | (ccls-member-hierarchy-node-name data) 97 | (ccls-member-hierarchy-node-field-name data)))) 98 | 99 | (defun ccls-member-hierarchy () 100 | (interactive) 101 | (ccls-tree--open 102 | (make-ccls-tree-client 103 | :name "member hierarchy" 104 | :mode-line-format (propertize "Member hierarchy" 'face 'ccls-tree-mode-line-face) 105 | :top-line-f (lambda () (propertize "Members of" 'face 'ccls-tree-mode-line-face)) 106 | :make-string-f 'ccls-member-hierarchy--make-string 107 | :read-node-f 'ccls-member-hierarchy--read-node 108 | :request-children-f 'ccls-member-hierarchy--request-children 109 | :request-init-f 'ccls-member-hierarchy--request-init))) 110 | 111 | (provide 'ccls-member-hierarchy) 112 | ;;; ccls-member-hierarchy.el ends here 113 | -------------------------------------------------------------------------------- /ccls-semantic-highlight.el: -------------------------------------------------------------------------------- 1 | ;;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Tobias Pisani 4 | ;; Copyright (C) 2018-2020 Fangrui Song 5 | 6 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 7 | ;; of this software and associated documentation files (the "Software"), to deal 8 | ;; in the Software without restriction, including without limitation the rights 9 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and-or sell 10 | ;; copies of the Software, and to permit persons to whom the Software is 11 | ;; furnished to do so, subject to the following conditions: 12 | 13 | ;; The above copyright notice and this permission notice shall be included in 14 | ;; all copies or substantial portions of the Software. 15 | 16 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | ;; SOFTWARE. 23 | 24 | ;;; Code: 25 | 26 | (require 'ccls-common) 27 | 28 | ;; --------------------------------------------------------------------- 29 | ;; Customization 30 | ;; --------------------------------------------------------------------- 31 | 32 | (defgroup ccls-sem nil 33 | "ccls semantic highlight." 34 | :group 'tools 35 | :group 'ccls) 36 | 37 | (defface ccls-skipped-range-face 38 | '((t :inherit shadow)) 39 | "The face used to mark skipped ranges." 40 | :group 'ccls-sem) 41 | 42 | (defvar ccls-sem-face-function 'ccls-sem--default-face 43 | "Function used to determine the face of a symbol in semantic highlight.") 44 | 45 | (defface ccls-sem-global-variable-face 46 | '((t :weight extra-bold)) 47 | "The additional face for global variables." 48 | :group 'ccls-sem) 49 | 50 | (defface ccls-sem-local-face 51 | '((t :weight normal)) 52 | "The additional face for local entities." 53 | :group 'ccls-sem) 54 | 55 | (defface ccls-sem-local-function-face 56 | '((t :inherit ccls-sem-local-face)) 57 | "The additional face for local functions." 58 | :group 'ccls-sem) 59 | 60 | (defface ccls-sem-member-face 61 | '((t :slant italic)) 62 | "The extra face applied to member functions/variables." 63 | :group 'ccls-sem) 64 | 65 | (defface ccls-sem-static-face 66 | '((t :weight bold)) 67 | "The additional face for variables with static storage." 68 | :group 'ccls-sem) 69 | 70 | (defface ccls-sem-static-field-face 71 | '((t :inherit ccls-sem-static-face)) 72 | "The additional face for static member variables." 73 | :group 'ccls-sem) 74 | 75 | (defface ccls-sem-static-method-face 76 | '((t :inherit ccls-sem-static-face)) 77 | "The additional face for static member functions." 78 | :group 'ccls-sem) 79 | 80 | (defcustom ccls-sem-function-faces [font-lock-function-name-face] 81 | "Faces for functions." 82 | :type '(repeat face) 83 | :group 'ccls-sem) 84 | 85 | (defcustom ccls-sem-macro-faces [font-lock-variable-name-face] 86 | "Faces for macros." 87 | :type '(repeat face) 88 | :group 'ccls-sem) 89 | 90 | (defcustom ccls-sem-namespace-faces [font-lock-constant-face] 91 | "Faces for namespaces." 92 | :type '(repeat face) 93 | :group 'ccls-sem) 94 | 95 | (defcustom ccls-sem-parameter-faces [font-lock-variable-name-face] 96 | "Faces for parameters." 97 | :type '(repeat face) 98 | :group 'ccls-sem) 99 | 100 | (defcustom ccls-sem-type-faces [font-lock-type-face] 101 | "Faces used to mark types." 102 | :type '(repeat face) 103 | :group 'ccls-sem) 104 | 105 | (defcustom ccls-sem-variable-faces [font-lock-variable-name-face] 106 | "Faces for variables." 107 | :type '(repeat face) 108 | :group 'ccls-sem) 109 | 110 | ;; Default colors used by `ccls-use-default-rainbow-sem-highlight' 111 | (defcustom ccls-sem-function-colors 112 | '("#e5b124" "#927754" "#eb992c" "#e2bf8f" "#d67c17" 113 | "#88651e" "#e4b953" "#a36526" "#b28927" "#d69855") 114 | "Default colors for `ccls-sem-function-faces'." 115 | :type '(repeat color) 116 | :group 'ccls-sem) 117 | 118 | (defcustom ccls-sem-macro-colors 119 | '("#e79528" "#c5373d" "#e8a272" "#d84f2b" "#a67245" 120 | "#e27a33" "#9b4a31" "#b66a1e" "#e27a71" "#cf6d49") 121 | "Default colors for `ccls-sem-macro-faces'." 122 | :type '(repeat color) 123 | :group 'ccls-sem) 124 | 125 | (defcustom ccls-sem-namespace-colors 126 | '("#429921" "#58c1a4" "#5ec648" "#36815b" "#83c65d" 127 | "#417b2f" "#43cc71" "#7eb769" "#58bf89" "#3e9f4a") 128 | "Default colors for `ccls-sem-namespace-faces'." 129 | :type '(repeat color) 130 | :group 'ccls-sem) 131 | 132 | (defcustom ccls-sem-parameter-colors 133 | '("#429921" "#58c1a4" "#5ec648" "#36815b" "#83c65d" 134 | "#417b2f" "#43cc71" "#7eb769" "#58bf89" "#3e9f4a") 135 | "Default colors for `ccls-sem-parameter-faces'." 136 | :type '(repeat color) 137 | :group 'ccls-sem) 138 | 139 | (defcustom ccls-sem-type-colors 140 | '("#e1afc3" "#d533bb" "#9b677f" "#e350b6" "#a04360" 141 | "#dd82bc" "#de3864" "#ad3f87" "#dd7a90" "#e0438a") 142 | "Default colors for `ccls-sem-type-faces'." 143 | :type '(repeat color) 144 | :group 'ccls-sem) 145 | 146 | (defcustom ccls-sem-variable-colors 147 | ccls-sem-parameter-colors 148 | "Default colors for `ccls-sem-variable-faces'." 149 | :type '(repeat color) 150 | :group 'ccls-sem) 151 | 152 | (defcustom ccls-enable-skipped-ranges 153 | t 154 | "Enable skipped ranges. 155 | Regions that are disabled by preprocessors will be displayed in shadow." 156 | :group 'ccls-sem 157 | :type 'boolean) 158 | 159 | (defcustom ccls-sem-highlight-method 160 | nil 161 | "The method used to draw semantic highlight. 162 | overlays are more accurate than font-lock, but slower. 163 | If nil, disable semantic highlight." 164 | :group 'ccls-sem 165 | :type '(radio 166 | (const nil) 167 | (const :tag "overlay" overlay) 168 | (const :tag "font-lock" font-lock))) 169 | 170 | ;; --------------------------------------------------------------------- 171 | ;; Semantic highlight 172 | ;; --------------------------------------------------------------------- 173 | 174 | (defvar-local ccls--skipped-ranges-overlays nil "Skipped ranges overlays.") 175 | (defvar-local ccls--sem-overlays nil "Semantic highlight overlays.") 176 | 177 | (eval-and-compile 178 | (lsp-interface 179 | (CclsLR (:L :R) nil) 180 | (CclsSemanticHighlight (:uri :symbols) nil) 181 | (CclsSkippedRanges (:uri :skippedRanges) nil)) 182 | (lsp-interface 183 | (CclsSemanticHighlightSymbol (:id :parentKind :kind :storage :ranges) nil))) 184 | 185 | (defun ccls--clear-sem-highlights () 186 | "." 187 | (pcase ccls-sem-highlight-method 188 | ('overlay 189 | (while ccls--sem-overlays 190 | (delete-overlay (pop ccls--sem-overlays)))) 191 | ('font-lock 192 | (font-lock-ensure)))) 193 | 194 | (defun ccls-sem--default-face (symbol) 195 | "Get semantic highlight face of SYMBOL." 196 | ;; https://github.com/ccls-project/ccls/blob/master/src/symbol.h 197 | (-let* (((&CclsSemanticHighlightSymbol :id :parent-kind :kind :storage) symbol) 198 | (fn0 (lambda (faces lo0 hi0) 199 | (let* ((n (length faces)) 200 | (lo (/ (* lo0 n) 1000)) 201 | (hi (/ (* hi0 n) 1000)) 202 | (idx (max 0 (if (= lo hi) (1- hi) (+ lo (% id (- hi lo))))))) 203 | (elt faces idx)))) 204 | (fn (lambda (faces) (elt faces (% id (length faces)))))) 205 | ;; ccls/src/indexer.h ClangSymbolKind 206 | ;; clang/Index/IndexSymbol.h clang::index::SymbolKind 207 | (pcase kind 208 | ;; Functions 209 | (6 `(,(funcall fn0 ccls-sem-function-faces 0 800) 210 | ccls-sem-member-face)) ; Method 211 | (9 `(,(funcall fn0 ccls-sem-function-faces 800 1000) 212 | ccls-sem-member-face)) ; Constructor 213 | (12 `(,(funcall fn0 ccls-sem-function-faces 0 1000) 214 | ,@(when (= storage 2) 215 | '(ccls-sem-local-function-face)))) ; Function 216 | (254 `(,(funcall fn0 ccls-sem-function-faces 0 1000) 217 | ccls-sem-static-method-face)) ; StaticMethod 218 | 219 | ;; Types 220 | (3 (funcall fn0 ccls-sem-namespace-faces 0 1000)) ; Namespace 221 | ((or 5 23) (funcall fn0 ccls-sem-type-faces 0 800)) ; Struct, Class 222 | (10 (funcall fn0 ccls-sem-type-faces 800 1000)) ; Enum 223 | (26 (funcall fn0 ccls-sem-type-faces 0 1000)) ; TypeParameter 224 | (252 `(,(funcall fn0 ccls-sem-type-faces 0 1000))) ; TypeAlias 225 | 226 | ;; Variables 227 | (13 `(,(funcall fn0 ccls-sem-variable-faces 0 1000) 228 | ,@(when (member parent-kind '(1 3)) 229 | '(ccls-sem-global-variable-face)) 230 | ,@(when (= storage 2) 231 | '(ccls-sem-static-face)))) ; Variable 232 | (253 (funcall fn0 ccls-sem-parameter-faces 0 1000)) ; Parameter 233 | (255 (funcall fn0 ccls-sem-macro-faces 0 1000)) ; Macro 234 | (8 `(,(funcall fn0 ccls-sem-variable-faces 0 1000) 235 | ,(if (= storage 2) 236 | 'ccls-sem-static-field-face 237 | 'ccls-sem-member-face 238 | ))) ; Field 239 | (22 `(,(funcall fn0 ccls-sem-variable-faces 0 1000) 240 | ccls-sem-member-face)) ; EnumMember 241 | 242 | (_ (funcall fn ccls-sem-variable-faces))))) 243 | 244 | (defun ccls--publish-semantic-highlight (_workspace params) 245 | "Publish semantic highlight information according to PARAMS." 246 | (when ccls-sem-highlight-method 247 | (-when-let* (((&CclsSemanticHighlight :uri :symbols) params) 248 | (file (lsp--uri-to-path uri)) 249 | (buffer (find-buffer-visiting file))) 250 | (with-current-buffer buffer 251 | (with-silent-modifications 252 | (ccls--clear-sem-highlights) 253 | (let (overlays) 254 | (seq-doseq (symbol symbols) 255 | (-when-let* ((face (funcall ccls-sem-face-function symbol))) 256 | (seq-doseq (range (lsp:ccls-semantic-highlight-symbol-ranges symbol)) 257 | (-let (((&CclsLR :l :r) range)) 258 | (push (list (1+ l) (1+ r) face) overlays))))) 259 | ;; The server guarantees the ranges are non-overlapping. 260 | (setq overlays (sort overlays (lambda (x y) (< (car x) (car y))))) 261 | (pcase ccls-sem-highlight-method 262 | ('font-lock 263 | (seq-doseq (x overlays) 264 | (set-text-properties (car x) (cadr x) 265 | `(fontified t face ,(caddr x) font-lock-face ,(caddr x))))) 266 | ('overlay 267 | (seq-doseq (x overlays) 268 | (let ((ov (make-overlay (car x) (cadr x)))) 269 | (overlay-put ov 'face (caddr x)) 270 | (overlay-put ov 'ccls-sem-highlight t) 271 | (push ov ccls--sem-overlays))))))))))) 272 | 273 | (defmacro ccls-use-default-rainbow-sem-highlight () 274 | "Use default rainbow semantic highlight theme." 275 | (require 'dash) 276 | `(progn 277 | ,@(cl-loop 278 | for kind in '("function" "macro" "namespace" "parameter" "type" "variable") append 279 | (let ((colors (intern (format "ccls-sem-%s-colors" kind)))) 280 | (append 281 | (--map-indexed 282 | `(defface ,(intern (format "ccls-sem-%s-face-%S" kind it-index)) 283 | '((t :foreground ,it)) "." :group 'ccls-sem) 284 | (symbol-value colors)) 285 | (list 286 | `(setq ,(intern (format "ccls-sem-%s-faces" kind)) 287 | (apply #'vector 288 | (cl-loop for i below (length ,colors) collect 289 | (intern (format "ccls-sem-%s-face-%S" ,kind i))))))))))) 290 | 291 | ;; --------------------------------------------------------------------- 292 | ;; Skipped ranges 293 | ;; --------------------------------------------------------------------- 294 | 295 | (defun ccls--clear-skipped-ranges () 296 | "Clean up overlays." 297 | (while ccls--skipped-ranges-overlays 298 | (delete-overlay (pop ccls--skipped-ranges-overlays)))) 299 | 300 | (defun ccls--publish-skipped-ranges (_workspace params) 301 | "Put overlays on (preprocessed) inactive regions according to PARAMS." 302 | (-let* (((&CclsSkippedRanges :uri :skipped-ranges) params) 303 | (file (lsp--uri-to-path uri))) 304 | (-when-let (buffer (find-buffer-visiting file)) 305 | (with-current-buffer buffer 306 | (with-silent-modifications 307 | (ccls--clear-skipped-ranges) 308 | (when ccls-enable-skipped-ranges 309 | (overlay-recenter (point-max)) 310 | (seq-doseq (range skipped-ranges) 311 | (let ((ov (make-overlay (lsp--position-to-point (lsp:range-start range)) 312 | (lsp--position-to-point (lsp:range-end range)) buffer))) 313 | (overlay-put ov 'face 'ccls-skipped-range-face) 314 | (overlay-put ov 'ccls-inactive t) 315 | (push ov ccls--skipped-ranges-overlays))))))))) 316 | 317 | (provide 'ccls-semantic-highlight) 318 | -------------------------------------------------------------------------------- /ccls-tree.el: -------------------------------------------------------------------------------- 1 | ;;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Tobias Pisani 4 | ;; Copyright (C) 2018 Fangrui Song 5 | 6 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 7 | ;; of this software and associated documentation files (the "Software"), to deal 8 | ;; in the Software without restriction, including without limitation the rights 9 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and-or sell 10 | ;; copies of the Software, and to permit persons to whom the Software is 11 | ;; furnished to do so, subject to the following conditions: 12 | 13 | ;; The above copyright notice and this permission notice shall be included in 14 | ;; all copies or substantial portions of the Software. 15 | 16 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | ;; SOFTWARE. 23 | 24 | ;;; Code: 25 | 26 | (require 'ccls-common) 27 | 28 | (require 'xref) 29 | 30 | ;; --------------------------------------------------------------------- 31 | ;; Customization 32 | ;; --------------------------------------------------------------------- 33 | 34 | (defgroup ccls-tree nil 35 | "ccls tree." 36 | :group 'tools 37 | :group 'ccls) 38 | 39 | (defcustom ccls-tree-initial-levels 2 40 | "." 41 | :type 'integer 42 | :group 'ccls-tree) 43 | 44 | (defface ccls-tree-root-face 45 | '((t (:height 1.5 :line-height 2.0))) 46 | "." 47 | :group 'ccls-tree) 48 | 49 | (defface ccls-tree-mouse-face 50 | '((t (:background "green"))) 51 | "." 52 | :group 'ccls-tree) 53 | 54 | (defface ccls-tree-icon-face 55 | '((t (:foreground "grey"))) 56 | "." 57 | :group 'ccls-tree) 58 | 59 | (defface ccls-tree-header-line-face 60 | '((t (:foreground "grey" :height 0.8))) 61 | "." 62 | :group 'ccls-tree) 63 | 64 | (defface ccls-tree-mode-line-face 65 | '((t (:foreground "grey" :slant italic))) 66 | "." 67 | :group 'ccls-tree) 68 | 69 | ;; --------------------------------------------------------------------- 70 | ;; Tree node 71 | ;; --------------------------------------------------------------------- 72 | 73 | (cl-defstruct ccls-tree-node 74 | parent ;; parent node 75 | has-children ;; whether this node has children 76 | children ;; a list of child nodes, if already requested 77 | expanded ;; is this node expanded? 78 | location ;; (filename . (hashmap '(("line" . line) ("char" . char))). Location to go to on click 79 | data ;; Arbitrary, client defined data 80 | ) 81 | 82 | (cl-defstruct ccls-tree-client 83 | name ;; name of client 84 | mode-line-format ;; mode-line format 85 | header-line-format ;; header-line format 86 | top-line-f ;; Function to draw in front of the first line 87 | make-string-f ;; Function to get the string for a node. form: (node depth) 88 | read-node-f ;; Function to read a node from a hashmap. form: (hashmap &optional parent) 89 | request-children-f ;; Function to request children for a node. form: (node) 90 | request-init-f ;; Function to request initial nodes. form: () 91 | ) 92 | 93 | (defvar-local ccls-tree--cur-client nil 94 | "Buffer tree client.") 95 | 96 | (defvar-local ccls-tree-calling t 97 | "When non-nil, visit the node when the selected line changes.") 98 | 99 | (defun ccls-tree--read-node (data &optional parent) 100 | (funcall (ccls-tree-client-read-node-f ccls-tree--cur-client) data parent)) 101 | 102 | (defun ccls-tree--make-string (node depth) 103 | "Propertize the name of NODE with the correct properties" 104 | (funcall (ccls-tree-client-make-string-f ccls-tree--cur-client) node depth)) 105 | 106 | (defun ccls-tree-node--request-children (node) 107 | (funcall (ccls-tree-client-request-children-f ccls-tree--cur-client) node)) 108 | 109 | (defun ccls-tree--request-init (client) 110 | (funcall (ccls-tree-client-request-init-f client))) 111 | 112 | (defun ccls-tree--draw-top-line () 113 | (-some-> (ccls-tree-client-top-line-f ccls-tree--cur-client) 114 | (funcall) 115 | (concat "\n") 116 | (insert))) 117 | 118 | ;; --------------------------------------------------------------------- 119 | ;; Visualization 120 | ;; --------------------------------------------------------------------- 121 | 122 | (defvar-local ccls-tree--root-nodes nil 123 | ".") 124 | 125 | (defvar-local ccls-tree--visible-root nil 126 | ".") 127 | 128 | (defvar-local ccls-tree--origin-buffer nil 129 | "Buffer that the tree was opened from.") 130 | (defvar-local ccls-tree--origin-win nil 131 | "Win that the tree was opened from.") 132 | 133 | (defvar-local ccls-tree--opoint 1 134 | "The original point of the buffer that the tree was opened from.") 135 | 136 | (defun ccls-tree--refresh () 137 | (let ((p (point)) 138 | (inhibit-read-only t)) 139 | (erase-buffer) 140 | (setf (ccls-tree-node-expanded ccls-tree--visible-root) t) 141 | (ccls-tree--draw-top-line) 142 | (ccls-tree--insert-node ccls-tree--visible-root 0 1 0) 143 | (goto-char p))) 144 | 145 | (defun ccls-tree--insert-node (node number nchildren depth) 146 | (let* ((prefix (ccls-tree--make-prefix node number nchildren depth)) 147 | (name (ccls-tree--make-string node depth))) 148 | (insert (if (= depth 0) 149 | (propertize (concat prefix name "\n") 150 | 'depth depth 151 | 'face 'ccls-tree-root-face 152 | 'ccls-tree-node node) 153 | (propertize (concat prefix name "\n") 154 | 'depth depth 155 | 'ccls-tree-node node))) 156 | (when (or (ccls-tree-node-expanded node)) 157 | (when (and (ccls-tree-node-has-children node) 158 | (null (ccls-tree-node-children node))) 159 | (setf (ccls-tree-node-children node) 160 | (ccls-tree-node--request-children node))) 161 | (let ((children (ccls-tree-node-children node))) 162 | (--map-indexed (ccls-tree--insert-node it it-index (length children) (+ depth 1)) 163 | children))))) 164 | 165 | (defun ccls-tree--make-prefix (node number nchildren depth) 166 | "." 167 | (let* ((padding (if (= depth 0) "" (make-string (* 2 (- depth 1)) ?\ ))) 168 | (symbol (if (= depth 0) 169 | (if (ccls-tree-node-parent node) 170 | "◀ " 171 | "") 172 | (if (ccls-tree-node-has-children node) 173 | (if (ccls-tree-node-expanded node) "▶ " "▼ ") 174 | (if (eq number (- nchildren 1)) "└╸" "├╸"))))) 175 | (concat padding (propertize symbol 'face 'ccls-tree-icon-face)))) 176 | 177 | (defun ccls-tree--expand-levels (node levels) 178 | "Expand NODE and its children LEVELS down" 179 | (when (> levels 0) 180 | (setf (ccls-tree-node-expanded node) t) 181 | (--map (ccls-tree--expand-levels it (- levels 1)) 182 | (ccls-tree-node-children node)))) 183 | 184 | (defun ccls-tree--open (client) 185 | "." 186 | (let ((opoint (point)) 187 | (lsp-ws lsp--buffer-workspaces) 188 | (root-node-data (ccls-tree--request-init client)) 189 | (orig-buf (current-buffer)) 190 | (bufname (format "*ccls-tree %s*" (ccls-tree-client-name client)))) 191 | (with-current-buffer (get-buffer-create bufname) 192 | (ccls-tree-mode) 193 | (setq lsp--buffer-workspaces lsp-ws 194 | ccls-tree--cur-client client 195 | ccls-tree--origin-buffer orig-buf 196 | ccls-tree--origin-win (get-buffer-window orig-buf) 197 | ccls-tree--opoint opoint 198 | ccls-tree--root-nodes (when root-node-data (ccls-tree--read-node root-node-data)) 199 | ccls-tree--visible-root ccls-tree--root-nodes) 200 | (when (null ccls-tree--root-nodes) 201 | (user-error "Couldn't open tree from point")) 202 | (ccls-tree--refresh) 203 | (ccls-tree--expand-levels ccls-tree--visible-root ccls-tree-initial-levels) 204 | (ccls-tree--refresh) 205 | (setq header-line-format (ccls-tree-client-header-line-format ccls-tree--cur-client)) 206 | (setq mode-line-format (ccls-tree-client-mode-line-format ccls-tree--cur-client)) 207 | (goto-char 1) 208 | (forward-line)) 209 | (let ((win (display-buffer-in-side-window (get-buffer bufname) '((side . right))))) 210 | (set-window-margins win 1) 211 | (select-window win) 212 | (set-window-start win 1) 213 | (set-window-dedicated-p win t)))) 214 | 215 | (defun ccls-tree--node-at-point () 216 | (get-text-property (point) 'ccls-tree-node)) 217 | 218 | (defun ccls-tree--depth-at-point () 219 | (get-text-property (point) 'depth)) 220 | 221 | ;; --------------------------------------------------------------------- 222 | ;; Actions 223 | ;; --------------------------------------------------------------------- 224 | 225 | (defun ccls-tree-toggle-expand () 226 | "Toggle expansion of node at point" 227 | (interactive) 228 | (-when-let* ((node (ccls-tree--node-at-point))) 229 | (setf (ccls-tree-node-expanded node) 230 | (or (not (ccls-tree-node-expanded node)) 231 | (= 0 (ccls-tree--depth-at-point)))) 232 | (ccls-tree--refresh))) 233 | 234 | (defun ccls-tree-select-parent () 235 | "." 236 | (interactive) 237 | (let ((depth (ccls-tree--depth-at-point))) 238 | (if (null depth) 239 | (forward-line -1) 240 | (if (> depth 0) 241 | (while (and (>= (ccls-tree--depth-at-point) depth) 242 | (= 0 (forward-line -1)))) 243 | (-when-let* ((parent (ccls-tree-node-parent (ccls-tree--node-at-point)))) 244 | (setq ccls-tree--visible-root parent) 245 | (ccls-tree--refresh)))))) 246 | 247 | (defun ccls-tree-set-root () 248 | "Set root to current node" 249 | (interactive) 250 | (-when-let* ((node (ccls-tree--node-at-point))) 251 | (when (ccls-tree-node-has-children node) 252 | (setq ccls-tree--visible-root node) 253 | (setf (ccls-tree-node-expanded node) t) 254 | (ccls-tree--refresh)))) 255 | 256 | (defun ccls-tree-toggle-calling () 257 | "Toggle `ccls-tree-calling'." 258 | (interactive) 259 | (when (setq ccls-tree-calling (not ccls-tree-calling)) 260 | (ccls-tree-press))) 261 | 262 | (defun ccls-tree-press (&optional split-fn) 263 | "Jump to the location." 264 | (interactive) 265 | (-when-let* ((workspaces lsp--buffer-workspaces) 266 | (node (ccls-tree--node-at-point)) 267 | (unused (window-live-p ccls-tree--origin-win))) 268 | (with-selected-window ccls-tree--origin-win 269 | (when split-fn 270 | (funcall split-fn)) 271 | (find-file (car (ccls-tree-node-location node))) 272 | ;; TODO Extract lsp-ui-peek.el lsp-ui-peek--goto-xref 273 | (unless lsp--buffer-workspaces 274 | (setq lsp--buffer-workspaces workspaces) 275 | (lsp-mode 1) 276 | (dolist (workspace workspaces) 277 | (lsp--open-in-workspace workspace))) 278 | (goto-char (lsp--position-to-point (cdr (ccls-tree-node-location node)))) 279 | (recenter) 280 | (run-hooks 'xref-after-jump-hook)))) 281 | 282 | (defun ccls-tree-press-and-switch () 283 | "Switch window and jump to the location." 284 | (interactive) 285 | (ccls-tree-press) 286 | (when (window-live-p ccls-tree--origin-win) 287 | (select-window ccls-tree--origin-win))) 288 | 289 | (defun ccls-tree-press-and-horizontal-split () 290 | "Split window horizontally and jump to the location." 291 | (interactive) 292 | (ccls-tree-press #'split-window-horizontally) 293 | (when (window-live-p ccls-tree--origin-win) 294 | (select-window ccls-tree--origin-win))) 295 | 296 | (defun ccls-tree-press-and-vertical-split () 297 | "Split window vertically and jump to the location." 298 | (interactive) 299 | (ccls-tree-press #'split-window-vertically) 300 | (when (window-live-p ccls-tree--origin-win) 301 | (select-window ccls-tree--origin-win))) 302 | 303 | (defun ccls-tree-next-line (&optional arg) 304 | (interactive "p") 305 | (forward-line arg) 306 | (when ccls-tree-calling 307 | (ccls-tree-press))) 308 | 309 | (defun ccls-tree-prev-line (&optional arg) 310 | (interactive "p") 311 | (forward-line (- arg)) 312 | (when ccls-tree-calling 313 | (ccls-tree-press))) 314 | 315 | (defun ccls-tree-next-sibling (&optional _arg) 316 | (interactive "p") 317 | (-when-let* ((depth (ccls-tree--depth-at-point))) 318 | (while (and (forward-line 1) 319 | (< depth (or (ccls-tree--depth-at-point) 0)))) 320 | (when ccls-tree-calling 321 | (ccls-tree-press)))) 322 | 323 | (defun ccls-tree-prev-sibling (&optional _arg) 324 | (interactive "p") 325 | (-when-let* ((depth (ccls-tree--depth-at-point))) 326 | (while (and (forward-line -1) 327 | (< depth (or (ccls-tree--depth-at-point) 0)))) 328 | (when ccls-tree-calling 329 | (ccls-tree-press)))) 330 | 331 | (defun ccls-tree-expand-or-set-root () 332 | "If the node at point is unexpanded expand it, otherwise set it as root" 333 | (interactive) 334 | (-when-let* ((node (ccls-tree--node-at-point))) 335 | (when (ccls-tree-node-has-children node) 336 | (if (ccls-tree-node-expanded node) 337 | (ccls-tree-set-root) 338 | (ccls-tree-toggle-expand))))) 339 | 340 | (defun ccls-tree-collapse-or-select-parent () 341 | "If the node at point is expanded collapse it, otherwise select its parent." 342 | (interactive) 343 | (-when-let* ((node (ccls-tree--node-at-point))) 344 | (if (and (> (ccls-tree--depth-at-point) 0) 345 | (ccls-tree-node-expanded node)) 346 | (ccls-tree-toggle-expand) 347 | (ccls-tree-select-parent)))) 348 | 349 | (defun ccls-tree-quit () 350 | (interactive) 351 | (-when-let* ((buf ccls-tree--origin-buffer) 352 | (opoint ccls-tree--opoint) 353 | (unused (window-live-p ccls-tree--origin-win))) 354 | (with-selected-window ccls-tree--origin-win 355 | (switch-to-buffer buf) 356 | (goto-char opoint))) 357 | (quit-window)) 358 | 359 | (defun ccls-tree-yank-path () 360 | (interactive) 361 | (--if-let (-some-> (ccls-tree--node-at-point) (ccls-tree-node-location) (car) (kill-new)) 362 | (message (format "Yanked path: %s" (propertize it 'face 'font-lock-string-face))) 363 | (user-error "There is nothing to copy here"))) 364 | 365 | ;; --------------------------------------------------------------------- 366 | ;; Mode 367 | ;; --------------------------------------------------------------------- 368 | 369 | (defvar ccls-tree-mode-map 370 | (let ((map (make-sparse-keymap))) 371 | (define-key map (kbd "") #'ccls-tree-toggle-expand) 372 | (define-key map [mouse-1] #'ccls-tree-toggle-expand) 373 | (define-key map (kbd "c") #'ccls-tree-toggle-calling) 374 | (define-key map (kbd "f") #'ccls-tree-press) 375 | (define-key map (kbd "h") #'ccls-tree-collapse-or-select-parent) 376 | (define-key map (kbd "j") #'ccls-tree-next-line) 377 | (define-key map (kbd "k") #'ccls-tree-prev-line) 378 | (define-key map (kbd "J") #'ccls-tree-next-sibling) 379 | (define-key map (kbd "K") #'ccls-tree-prev-sibling) 380 | (define-key map (kbd "l") #'ccls-tree-expand-or-set-root) 381 | (define-key map (kbd "oh") #'ccls-tree-press-and-horizontal-split) 382 | (define-key map (kbd "ov") #'ccls-tree-press-and-vertical-split) 383 | (define-key map (kbd "oo") #'ccls-tree-press-and-switch) 384 | (define-key map (kbd "q") #'ccls-tree-quit) 385 | (define-key map (kbd "Q") #'quit-window) 386 | (define-key map (kbd "yy") #'ccls-tree-yank-path) 387 | (define-key map (kbd "RET") #'ccls-tree-press-and-switch) 388 | (define-key map (kbd "") #'ccls-tree-collapse-or-select-parent) 389 | (define-key map (kbd "") #'ccls-tree-expand-or-set-root) 390 | map) 391 | "Keymap for `ccls-tree-mode'.") 392 | 393 | (define-derived-mode ccls-tree-mode special-mode "ccls-tree" 394 | "Mode for ccls tree buffers") 395 | 396 | (provide 'ccls-tree) 397 | -------------------------------------------------------------------------------- /ccls.el: -------------------------------------------------------------------------------- 1 | ;;; ccls.el --- ccls client for lsp-mode -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017 Tobias Pisani 4 | ;; Copyright (C) 2018 Fangrui Song 5 | 6 | ;; Author: Tobias Pisani, Fangrui Song 7 | ;; Package-Version: 20200621 8 | ;; Version: 0.1 9 | ;; Homepage: https://github.com/emacs-lsp/emacs-ccls 10 | ;; Package-Requires: ((emacs "28.1") (lsp-mode "6.3.1") (dash "2.14.1")) 11 | ;; Keywords: languages, lsp, c++ 12 | 13 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 14 | ;; of this software and associated documentation files (the "Software"), to deal 15 | ;; in the Software without restriction, including without limitation the rights 16 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and-or sell 17 | ;; copies of the Software, and to permit persons to whom the Software is 18 | ;; furnished to do so, subject to the following conditions: 19 | 20 | ;; The above copyright notice and this permission notice shall be included in 21 | ;; all copies or substantial portions of the Software. 22 | 23 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | ;; SOFTWARE. 30 | ;; 31 | ;;; Commentary: 32 | 33 | ;; To enable, call (lsp) in c-mode-hook c++-mode-hook objc-mode-hook. 34 | ;; See more at https://github.com/MaskRay/ccls/wiki/Emacs 35 | 36 | ;;; Code: 37 | 38 | (require 'ccls-common) 39 | (require 'ccls-semantic-highlight) 40 | (require 'ccls-code-lens) 41 | (require 'ccls-tree) 42 | (require 'ccls-call-hierarchy) 43 | (require 'ccls-inheritance-hierarchy) 44 | (require 'ccls-member-hierarchy) 45 | 46 | (require 'seq) 47 | 48 | ;; --------------------------------------------------------------------- 49 | ;; Customization 50 | ;; --------------------------------------------------------------------- 51 | 52 | (defcustom ccls-executable 53 | "ccls" 54 | "Path of the ccls executable." 55 | :type 'file 56 | :group 'ccls) 57 | 58 | (defcustom ccls-args 59 | nil 60 | "Additional command line options passed to the ccls executable." 61 | :type '(repeat string) 62 | :group 'ccls) 63 | 64 | (defcustom ccls-library-folders-fn 65 | nil 66 | "Function which returns a list of library folders." 67 | :type 'function 68 | :group 'ccls) 69 | 70 | (defcustom ccls-initialization-options 71 | nil 72 | "initializationOptions" 73 | :group 'ccls 74 | :type 'alist) 75 | (put 'ccls-initialization-options 'safe-local-variable 'listp) 76 | 77 | (defcustom ccls-root-files 78 | '(".ccls-root") 79 | "A list of files considered to mark the root of a ccls project." 80 | :type '(repeat string) 81 | :group 'ccls) 82 | 83 | ;; --------------------------------------------------------------------- 84 | ;; Other ccls-specific methods 85 | ;; --------------------------------------------------------------------- 86 | ;; 87 | 88 | (eval-and-compile 89 | (lsp-interface 90 | (CclsQueryFileDef (:path :args :language :dependencies :includes :skipped-ranges) nil))) 91 | 92 | (defun ccls-info () 93 | (lsp-request "$ccls/info" (make-hash-table))) 94 | 95 | (defun ccls-file-info (&optional extra) 96 | (lsp-request "$ccls/fileInfo" 97 | (append (lsp--text-document-position-params) extra))) 98 | 99 | (defun ccls-preprocess-file (&optional output-buffer) 100 | "Preprocess selected buffer." 101 | (interactive) 102 | (lsp--cur-workspace-check) 103 | (-when-let* ((mode major-mode) 104 | (info (ccls-file-info)) 105 | (args (seq-into (lsp:ccls-query-file-def-args info) 'vector)) 106 | (new-args (let ((i 0) ret) 107 | (while (< i (length args)) 108 | (let ((arg (elt args i))) 109 | (cond 110 | ((string= arg "-o") (cl-incf i)) 111 | ((string-match-p "\\`-o.+" arg)) 112 | (t (push arg ret)))) 113 | (cl-incf i)) 114 | (nreverse ret)))) 115 | (with-current-buffer (or output-buffer 116 | (get-buffer-create 117 | (format "*lsp-ccls preprocess %s*" (buffer-name)))) 118 | (pop-to-buffer (current-buffer)) 119 | (with-silent-modifications 120 | (erase-buffer) 121 | (insert (format "// Generated by: %s" 122 | (combine-and-quote-strings new-args))) 123 | (insert (with-output-to-string 124 | (with-current-buffer standard-output 125 | (apply #'process-file (car new-args) nil t nil "-E" (cdr new-args))))) 126 | (delay-mode-hooks (funcall mode)) 127 | (setq buffer-read-only t))))) 128 | 129 | (defun ccls-reload () 130 | "Reset database and reload cached index files." 131 | (interactive) 132 | (lsp-notify "$ccls/reload" (list :whitelist [] :blacklist []))) 133 | 134 | (defun ccls-navigate (direction) 135 | "Navigate to a nearby outline symbol. 136 | DIRECTION can be \"D\", \"L\", \"R\" or \"U\"." 137 | (lsp-find-custom "$ccls/navigate" `(:direction ,direction))) 138 | 139 | ;; --------------------------------------------------------------------- 140 | ;; Register lsp client 141 | ;; --------------------------------------------------------------------- 142 | 143 | (defun ccls--suggest-project-root () 144 | (and (memq major-mode '(c-mode c++-mode cuda-mode objc-mode)) 145 | (when-let (dir (cl-some #'(lambda (file) (locate-dominating-file default-directory file)) 146 | ccls-root-files)) 147 | (expand-file-name dir)))) 148 | 149 | (cl-defmethod lsp-execute-command 150 | ((_server (eql ccls)) (command (eql ccls.xref)) arguments) 151 | (when-let ((xrefs (lsp--locations-to-xref-items 152 | (lsp--send-execute-command "ccls.xref" arguments)))) 153 | (lsp-show-xrefs xrefs nil t))) 154 | 155 | (advice-add 'lsp--suggest-project-root :before-until #'ccls--suggest-project-root) 156 | 157 | (lsp-register-client 158 | (make-lsp-client 159 | :new-connection (lsp-stdio-connection (lambda () (cons ccls-executable ccls-args))) 160 | :activation-fn (lsp-activate-on "c" "cpp" "objective-c" "cuda") 161 | :server-id 'ccls 162 | :multi-root nil 163 | :notification-handlers 164 | (lsp-ht ("$ccls/publishSkippedRanges" #'ccls--publish-skipped-ranges) 165 | ("$ccls/publishSemanticHighlight" #'ccls--publish-semantic-highlight)) 166 | :initialization-options (lambda () ccls-initialization-options) 167 | :library-folders-fn (lambda (&rest args) 168 | (if ccls-library-folders-fn (apply ccls-library-folders-fn args))))) 169 | 170 | (provide 'ccls) 171 | ;;; ccls.el ends here 172 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | test 2 | --------------------------------------------------------------------------------