├── pics ├── rename.png ├── override-result.png ├── auto-complete-ido.png ├── company-mode-popup.png ├── auto-complete-popup.png ├── override-suggestions.png ├── syntax-error-flycheck.png ├── company-mode-doc-buffer.png ├── company-mode-parameters.png ├── refactoring-suggestions.png ├── company-mode-popup-complex.png ├── navigate-to-current-type-member.png ├── auto-complete-popup-documentation.png ├── navigate-to-type-in-current-file.png └── build-solution-in-compilation-buffer.png ├── example-config-for-evil-mode.el ├── README.md └── omnisharp.el /pics/rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/rename.png -------------------------------------------------------------------------------- /pics/override-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/override-result.png -------------------------------------------------------------------------------- /pics/auto-complete-ido.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/auto-complete-ido.png -------------------------------------------------------------------------------- /pics/company-mode-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/company-mode-popup.png -------------------------------------------------------------------------------- /pics/auto-complete-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/auto-complete-popup.png -------------------------------------------------------------------------------- /pics/override-suggestions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/override-suggestions.png -------------------------------------------------------------------------------- /pics/syntax-error-flycheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/syntax-error-flycheck.png -------------------------------------------------------------------------------- /pics/company-mode-doc-buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/company-mode-doc-buffer.png -------------------------------------------------------------------------------- /pics/company-mode-parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/company-mode-parameters.png -------------------------------------------------------------------------------- /pics/refactoring-suggestions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/refactoring-suggestions.png -------------------------------------------------------------------------------- /pics/company-mode-popup-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/company-mode-popup-complex.png -------------------------------------------------------------------------------- /pics/navigate-to-current-type-member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/navigate-to-current-type-member.png -------------------------------------------------------------------------------- /pics/auto-complete-popup-documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/auto-complete-popup-documentation.png -------------------------------------------------------------------------------- /pics/navigate-to-type-in-current-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/navigate-to-type-in-current-file.png -------------------------------------------------------------------------------- /pics/build-solution-in-compilation-buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/omnisharp-emacs/master/pics/build-solution-in-compilation-buffer.png -------------------------------------------------------------------------------- /example-config-for-evil-mode.el: -------------------------------------------------------------------------------- 1 | ;; Example evil-mode config 2 | 3 | (evil-define-key 'insert omnisharp-mode-map 4 | (kbd "M-.") 'omnisharp-auto-complete) 5 | 6 | (evil-define-key 'normal omnisharp-mode-map 7 | (kbd "") 'omnisharp-go-to-definition) 8 | 9 | (evil-define-key 'normal omnisharp-mode-map 10 | (kbd "g u") 'omnisharp-find-usages) 11 | 12 | (evil-define-key 'normal omnisharp-mode-map 13 | (kbd "g o") 'omnisharp-go-to-definition) 14 | 15 | (evil-define-key 'normal omnisharp-mode-map 16 | (kbd "g r") 'omnisharp-run-code-action-refactoring) 17 | 18 | (evil-define-key 'normal omnisharp-mode-map 19 | (kbd "g R") 'omnisharp-rename) 20 | 21 | (evil-define-key 'normal omnisharp-mode-map 22 | (kbd ", i") 'omnisharp-current-type-information) 23 | 24 | (evil-define-key 'insert omnisharp-mode-map 25 | (kbd ".") 'omnisharp-add-dot-and-auto-complete) 26 | 27 | (evil-define-key 'normal omnisharp-mode-map 28 | (kbd ", n t") 'omnisharp-navigate-to-current-file-member) 29 | 30 | (evil-define-key 'normal omnisharp-mode-map 31 | (kbd ", n s") 'omnisharp-navigate-to-solution-member) 32 | 33 | (evil-define-key 'normal omnisharp-mode-map 34 | (kbd ", n f") 'omnisharp-navigate-to-solution-file-then-file-member) 35 | 36 | (evil-define-key 'normal omnisharp-mode-map 37 | (kbd ", n F") 'omnisharp-navigate-to-solution-file) 38 | 39 | (evil-define-key 'normal omnisharp-mode-map 40 | (kbd ", n r") 'omnisharp-navigate-to-region) 41 | 42 | (evil-define-key 'normal omnisharp-mode-map 43 | (kbd "") 'omnisharp-show-last-auto-complete-result) 44 | 45 | (evil-define-key 'insert omnisharp-mode-map 46 | (kbd "") 'omnisharp-show-last-auto-complete-result) 47 | 48 | (evil-define-key 'normal omnisharp-mode-map 49 | (kbd ",.") 'omnisharp-show-overloads-at-point) 50 | 51 | 52 | ;; Speed up auto-complete on mono drastically. This comes with the 53 | ;; downside that documentation is impossible to fetch. 54 | (setq omnisharp-auto-complete-want-documentation nil) 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # omnisharp-emacs 2 | 3 | omnisharp-emacs is a port of the awesome [OmniSharp][] server to the 4 | Emacs text editor. It provides IDE-like features for editing files in 5 | C# solutions in Emacs, provided by an OmniSharp server instance that 6 | works in the background. 7 | 8 | ## Project maturity 9 | Lacks a better UI and a good default configuration. 10 | 11 | ## Features 12 | 13 | * Contextual code completion (i.e. auto-complete / IntelliSense) using 14 | [popup.el][] or [ido-mode][] or [company-mode][] if it is installed. 15 | * Popup.el and company-mode provide a more sophisticated 16 | interface, with the possibility to fall back on all of ido's 17 | flexible matching power. 18 | * Also shows documentation like other IDEs 19 | * Show type of the current symbol in the minibuffer. With prefix 20 | argument, add it to kill ring. 21 | * Navigation helpers 22 | * Go to definition of a type/variable/method etc. With the prefix 23 | argument (C-u), use another window. 24 | * Find usages of the current symbol in the solution 25 | * Find implementations/derived types of the current type 26 | * Go to definition of a type in the current file with [ido-mode][] 27 | (fast). 28 | * Go to definition of a member in the current type with 29 | [ido-mode][] (likewise fast :)). 30 | * Go to region / endregion in current file 31 | * Go to any member in the solution (property, method etc.) 32 | * Go to file, then go to member (type, property, method) in that 33 | file. 34 | * Rename the current symbol and all references to it 35 | * Rename only semantic references ("smart" rename) 36 | * Rename as verbatim text ("dumb" rename) 37 | * Solution manipulation 38 | * Add/remove the current file 39 | * Add/remove selected files in the dired directory editor 40 | * Override selected superclass member 41 | * Run a refactoring on the current position 42 | * Uses the refactorings from the NRefactory library, which is also 43 | used by the MonoDevelop and SharpDevelop IDEs 44 | * Solution building 45 | * The user may choose whether they want to build in the emacs 46 | `*compilation*` buffer or at OmniSharp's end (non-asynchronous, 47 | that is, blocking) 48 | * Jump to errors like in normal `*compilation*` output 49 | * Format the current buffer 50 | * Currently only one formatting style supported, easy to add more. 51 | * Syntax checker for parse errors 52 | * Runs using the provided [Flycheck][] checker in the background. 53 | * OmniSharp server instance manipulation 54 | * Reload solution 55 | * Stop server 56 | 57 | ## Details 58 | 59 | ### Autocompletion 60 | 61 | #### company-mode interface 62 | 63 | company-mode showing parameters and return values, and the selected 64 | function description in the minibuffer. As you can see, the completion 65 | works with non-trivial code. 66 | 67 | ![](pics/company-mode-popup-complex.png) 68 | 69 | company-mode also allows for yasnippet-like template completion of 70 | method parameters. 71 | 72 | ![](pics/company-mode-parameters.png) 73 | 74 | Pressing F1 with a candidate selected in the the company-mode popup 75 | shows a buffer with documentation. 76 | 77 | ![](pics/company-mode-doc-buffer.png) 78 | 79 | 80 | #### popup.el interface 81 | 82 | ![](pics/auto-complete-popup.png) 83 | 84 | popup.el with documentation. The documentation may be disabled if you 85 | need the screen space. There is an option to show documentation in a 86 | help buffer. 87 | 88 | ![](pics/auto-complete-popup-documentation.png) 89 | 90 | #### Ido interface 91 | 92 | Ido allows for flexible matching of all text that the completions 93 | have. Each pressed character will narrow the list down to fewer 94 | options. It's also possible to do a cross search at any point with a 95 | new search term by pressing C-SPC. 96 | 97 | This makes it really easy to e.g. narrow the list down to members that 98 | handle a specific type, such as bool. 99 | 100 | ![](pics/auto-complete-ido.png) 101 | 102 | ### Go to type in current file 103 | This is a standard functionality in e.g. Visual Studio. 104 | The types are shown in the order they occur in the source file. 105 | 106 | ![](pics/navigate-to-type-in-current-file.png) 107 | 108 | ### Go to member in current type 109 | This too is standard in various IDEs. Using ido makes navigating fast 110 | and intuitive. 111 | The members too are shown in the order they occur in the source file. 112 | 113 | ![](pics/navigate-to-current-type-member.png) 114 | 115 | ### Rename 116 | Renaming suggests the current type as a basis. 117 | 118 | ![](pics/rename.png) 119 | 120 | ### Overriding members 121 | When invoked, displays a list of possible override targets. 122 | 123 | ![](pics/override-suggestions.png) 124 | 125 | When a target is chosen, a stub member is inserted. 126 | 127 | ![](pics/override-result.png) 128 | 129 | ### Refactoring suggestions 130 | For now, this must be manually invoked. It can do different things 131 | depending on the symbol under point. In this picture it has been 132 | invoked on a method parameter. 133 | 134 | ![](pics/refactoring-suggestions.png) 135 | 136 | ### Solution building 137 | Here is an example of an asynchronous build within Emacs. It works by 138 | getting the build command from the backend and executing that in the 139 | compilation buffer. 140 | 141 | ![](pics/build-solution-in-compilation-buffer.png) 142 | 143 | ### Syntax errors checking 144 | It is possible to check the current buffer for syntax errors using the 145 | flycheck library. This is done asynchronously, and errors are shown 146 | when found. Note that this is not a type checker, only syntax is 147 | currently checked. 148 | 149 | ![](pics/syntax-error-flycheck.png) 150 | 151 | To start the check, use (omnisharp-start-flycheck) or select it in the 152 | menu. The check will then be performed after the current buffer has 153 | been idle for a certain number of seconds. Currently the default is 154 | 0.5 seconds. 155 | 156 | To make syntax checking start sooner/later, use: 157 | ``` 158 | (setq flycheck-idle-change-delay 2) ; in seconds 159 | ``` 160 | 161 | ## Installation 162 | 163 | This supports Emacs 24.3 and above at least. It has been tested on 164 | Ubuntu 12.04 (Precise), on Windows 7 and on OSX. 165 | 166 | To install, use [MELPA][]. 167 | After MELPA is installed, use 168 | 169 | ``` 170 | M-x package-install omnisharp RET 171 | ``` 172 | to install. 173 | 174 | omnisharp-emacs depends on the external program `curl` for accessing 175 | the background OmniSharp server process. You need to ensure this is 176 | installed and can be found by Emacs. 177 | 178 | To automatically load omnisharp-emacs when editing csharp files, add 179 | something like this to your csharp-mode-hook: 180 | 181 | ``` 182 | (add-hook 'csharp-mode-hook 'omnisharp-mode) 183 | ``` 184 | 185 | Start an OmniSharp server process on a solution, and you should have 186 | access to all of this program's functions. You probably need to 187 | create a custom configuration for accessing them in your normal 188 | coding sessions. 189 | 190 | To enable company-mode autocompletion, omnisharp requires at least 191 | version 0.6.11 of company-mode to be installed. Then add the following 192 | to your dotemacs: 193 | 194 | ``` 195 | (eval-after-load 'company 196 | '(add-to-list 'company-backends 'company-omnisharp)) 197 | ``` 198 | 199 | company-mode completion will only trigger when omnisharp-mode is active. 200 | 201 | * * * * * 202 | 203 | Pull requests welcome! 204 | 205 | [OmniSharp]: https://github.com/nosami/OmniSharpServer 206 | [popup.el]: https://github.com/auto-complete/popup-el 207 | [company-mode]: http://company-mode.github.io 208 | [ido-mode]: http://www.emacswiki.org/emacs/InteractivelyDoThings 209 | [Flycheck]: https://github.com/lunaryorn/flycheck 210 | [MELPA]: http://melpa.milkbox.net/#installing 211 | -------------------------------------------------------------------------------- /omnisharp.el: -------------------------------------------------------------------------------- 1 | ;;; omnisharp.el --- Omnicompletion (intellisense) and more for C# 2 | ;; Copyright (C) 2013 Mika Vilpas (GPLv3) 3 | ;; Author: Mika Vilpas 4 | ;; Version: 1.4 5 | ;; Url: https://github.com/sp3ctum/omnisharp-emacs 6 | ;; Package-Requires: ((json "1.2") (dash "1.8.0") (popup "0.5") (auto-complete "1.4") (flycheck "0.13")) 7 | ;; Keywords: csharp c# IDE auto-complete intellisense 8 | 9 | ;;; Commentary: 10 | ;; omnisharp-emacs is a port of the awesome OmniSharp server to the 11 | ;; Emacs text editor. It provides IDE-like features for editing files 12 | ;; in C# solutions in Emacs, provided by an OmniSharp server instance 13 | ;; that works in the background. 14 | ;; 15 | ;; See the project home page for more information. 16 | 17 | ;; Work in progress! Judge gently! 18 | (require 'json) 19 | (with-no-warnings 20 | (require 'cl)) 21 | (require 'files) 22 | (require 'ido) 23 | (require 'thingatpt) 24 | (require 'dash) 25 | (require 'compile) 26 | (require 'dired) 27 | (require 'popup) 28 | (require 'etags) 29 | (require 'flycheck) 30 | (require 'auto-complete) 31 | 32 | (defgroup omnisharp () 33 | "Omnisharp-emacs is a port of the awesome OmniSharp server to 34 | the Emacs text editor. It provides IDE-like features for editing 35 | files in C# solutions in Emacs, provided by an OmniSharp server 36 | instance that works in the background." 37 | :group 'external 38 | :group 'csharp) 39 | 40 | ;;; Code: 41 | (defcustom omnisharp-host "http://localhost:2000/" 42 | "Currently expected to end with a / character." 43 | :group 'omnisharp 44 | :type 'string) 45 | 46 | (defcustom omnisharp-timeout 1 47 | "Timeout, in seconds, after which to abort stalling queries to the 48 | OmniSharp server." 49 | :group 'omnisharp 50 | :type 'integer) 51 | 52 | (defvar omnisharp-auto-complete-popup-want-isearch t 53 | "Whether to automatically start isearch when auto-completing.") 54 | 55 | (defvar omnisharp--find-usages-buffer-name "* OmniSharp : Usages *" 56 | "The name of the temporary buffer that is used to display the 57 | results of a 'find usages' call.") 58 | 59 | (defvar omnisharp--find-implementations-buffer-name "* OmniSharp : Implementations *" 60 | "The name of the temporary buffer that is used to display the 61 | results of a 'find implementations' call.") 62 | 63 | (defvar omnisharp--last-auto-complete-result-buffer-name 64 | "* OmniSharp : Last auto-complete result *" 65 | "The name of the temporary buffer that is used to display the 66 | results of an auto-complete call.") 67 | 68 | (defvar omnisharp--last-auto-complete-result-buffer-header 69 | (concat 70 | "Last auto-complete result:" 71 | "\n\n") 72 | "The header for the temporary buffer that is used to display the 73 | results of an auto-complete call.") 74 | 75 | (defcustom omnisharp-auto-complete-popup-help-delay nil 76 | "The timeout after which the auto-complete popup will show its help 77 | popup. Disabled by default because the help is often scrambled and 78 | looks bad." 79 | :group 'omnisharp 80 | :type '(choice (const :tag "disabled" nil) 81 | integer)) 82 | 83 | (defcustom omnisharp-auto-complete-popup-persist-help t 84 | "Whether to keep the help window (accessed by pressing f1 while the 85 | popup window is active) open after any other key is 86 | pressed. Defaults to true." 87 | :group 'omnisharp 88 | :type '(choice (const :tag "Yes" t) 89 | (const :tag "No" nil))) 90 | 91 | (defvar-local 92 | omnisharp--last-buffer-specific-auto-complete-result 93 | nil 94 | "Contains the last result of an autocomplete query.") 95 | 96 | (defcustom omnisharp-auto-complete-want-documentation t 97 | "Whether to include auto-complete documentation for each and every 98 | response. This may be set to nil to get a speed boost for 99 | completions." 100 | :group 'omnisharp 101 | :type '(choice (const :tag "Yes" t) 102 | (const :tag "No" nil))) 103 | 104 | (defvar omnisharp-auto-complete-popup-keymap 105 | (let ((keymap (make-sparse-keymap))) 106 | (set-keymap-parent keymap popup-menu-keymap) 107 | 108 | (define-key keymap (kbd "") 'omnisharp--popup-to-ido) 109 | keymap) 110 | "The keymap used when displaying an autocomplete result in a popup 111 | menu.") 112 | 113 | (defvar omnisharp-find-usages-header 114 | (concat "Usages in the current solution:" 115 | "\n\n") 116 | "This is shown at the top of the result buffer when 117 | omnisharp-find-usages is called.") 118 | 119 | (defvar omnisharp-find-implementations-header 120 | (concat "Implementations of the current interface / class:" 121 | "\n\n") 122 | "This is shown at the top of the result buffer when 123 | omnisharp-find-implementations is called.") 124 | 125 | (defvar omnisharp--auto-complete-display-backend 126 | 'popup 127 | "Defines what auto-complete result displaying backend to use when 128 | showing autocomplete results to the user. Valid values are found in 129 | omnisharp--auto-complete-display-backends-alist.") 130 | 131 | (defvar omnisharp--auto-complete-display-backends-alist 132 | '((popup . omnisharp--auto-complete-display-function-popup) 133 | (ido . omnisharp--auto-complete-display-function-ido)) 134 | "Holds an alist of all available auto-complete display backends. 135 | See the documentation for the variable 136 | omnisharp--auto-complete-display-backend for more information.") 137 | 138 | (defvar omnisharp--show-last-auto-complete-result-frontend 139 | 'plain-buffer 140 | "Defines the function that is used for displaying the last 141 | auto-complete result with various functions. Valid values are found in 142 | omnisharp--auto-complete-display-backends-alist.") 143 | 144 | (defvar omnisharp--show-last-auto-complete-result-frontends-alist 145 | '((plain-buffer . omnisharp--show-last-auto-complete-result-in-plain-buffer)) 146 | "Holds an alist of all available frontends for displaying the last 147 | auto-complete result. See the documentation for the variable 148 | omnisharp--show-last-auto-complete-result-frontend for more 149 | information.") 150 | 151 | (defcustom omnisharp-code-format-expand-tab t 152 | "Whether to expand tabs to spaces in code format requests." 153 | :group 'omnisharp 154 | :type '(choice (const :tag "Yes" t) 155 | (const :tag "No" nil))) 156 | 157 | (defvar omnisharp-mode-map 158 | (let ((map (make-sparse-keymap))) 159 | ;; TODO add good default keys here 160 | ;;(define-key map (kbd "C-c f") 'insert-foo) 161 | map) 162 | "Keymap for omnisharp-mode.") 163 | 164 | ;; Note that emacs seems to internally expect windows paths to have 165 | ;; forward slashes. 166 | (defcustom omnisharp--windows-curl-tmp-file-path 167 | "C:/omnisharp-tmp-file.cs" 168 | "The full file path where to save temporary stuff that gets sent to 169 | the OmniSharp API. Only used on Windows. 170 | Must be writable by the current user." 171 | :group 'omnisharp 172 | :type 'file) 173 | 174 | ;;;###autoload 175 | (define-minor-mode omnisharp-mode 176 | "Omnicompletion (intellisense) and more for C# using an OmniSharp 177 | server backend." 178 | :lighter " omnisharp" 179 | :global nil 180 | :keymap omnisharp-mode-map 181 | (when omnisharp-imenu-support 182 | (if omnisharp-mode 183 | (progn 184 | (setq imenu-create-index-function 'omnisharp-imenu-create-index) 185 | (imenu-add-menubar-index)) 186 | (setq imenu-create-index-function 'imenu-default-create-index-function)))) 187 | 188 | (easy-menu-define omnisharp-mode-menu omnisharp-mode-map 189 | "Menu for omnisharp-mode" 190 | '("OmniSharp" 191 | ("Auto-complete" 192 | ["at point" omnisharp-auto-complete] 193 | ["Add . and complete members" omnisharp-add-dot-and-auto-complete] 194 | ["Override superclass member" omnisharp-auto-complete-overrides] 195 | ["Show last result" omnisharp-show-last-auto-complete-result] 196 | ["Show overloads at point" omnisharp-show-overloads-at-point]) 197 | 198 | ("Navigate to.." 199 | ["Definition at point" omnisharp-go-to-definition] 200 | ["Current file member" omnisharp-navigate-to-current-file-member] 201 | ["Type in current file" omnisharp-navigate-to-type-in-current-file] 202 | ["Solution member" omnisharp-navigate-to-solution-member] 203 | ["File in solution" omnisharp-navigate-to-solution-file] 204 | ["Region in current file" omnisharp-navigate-to-region]) 205 | 206 | ("OmniSharp server" 207 | ["Reload solution" omnisharp-reload-solution] 208 | ["Stop OmniSharp server" omnisharp-stop-server]) 209 | 210 | ("Current symbol" 211 | ["Show type" omnisharp-current-type-information] 212 | ["Show type and add it to kill ring" omnisharp-current-type-information-to-kill-ring] 213 | ["Find usages" omnisharp-find-usages] 214 | ["Find implementations" omnisharp-find-implementations] 215 | ["Rename" omnisharp-rename] 216 | ["Rename interactively" omnisharp-rename-interactively]) 217 | 218 | ("Solution actions" 219 | ["Add current file to solution" omnisharp-add-to-solution-current-file] 220 | ["Remove current file from solution" omnisharp-remove-from-project-current-file] 221 | ["Add marked files in dired to solution" omnisharp-add-to-solution-dired-selected-files] 222 | ["Remove marked files in dired from solution" omnisharp-remove-from-project-current-file] 223 | ["Add reference to dll or project" omnisharp-add-reference] 224 | ["Build solution in emacs" omnisharp-build-in-emacs] 225 | ["Start syntax check" omnisharp-start-flycheck]) 226 | 227 | ["Run contextual code action / refactoring at point" omnisharp-run-code-action-refactoring] 228 | ["Run code format on current buffer" omnisharp-code-format] 229 | )) 230 | 231 | (defun omnisharp-get-host () 232 | "Makes sure omnisharp-host is ended by / " 233 | (if (string= (substring omnisharp-host -1 ) "/") 234 | omnisharp-host 235 | (concat omnisharp-host "/"))) 236 | 237 | (defun omnisharp-reload-solution () 238 | "Reload the current solution." 239 | (interactive) 240 | (omnisharp-post-message-curl 241 | (concat (omnisharp-get-host) "reloadsolution") 242 | ;; no params needed 243 | nil)) 244 | 245 | (defun omnisharp-go-to-definition (&optional other-window) 246 | "Jump to the definition of the symbol under point. With prefix 247 | argument, use another window." 248 | (interactive "P") 249 | (let* ((json-result (omnisharp-post-message-curl-as-json 250 | (concat (omnisharp-get-host) "gotodefinition") 251 | (omnisharp--get-common-params))) 252 | (filename (cdr (assoc 'FileName json-result)))) 253 | (if (null filename) 254 | (message 255 | "Cannot go to definition as none was returned by the API.") 256 | (omnisharp-go-to-file-line-and-column json-result other-window)))) 257 | 258 | (defun omnisharp-go-to-definition-other-window () 259 | "Do `omnisharp-go-to-definition' displaying the result in a different window." 260 | (interactive) 261 | (omnisharp-go-to-definition t)) 262 | 263 | (defun omnisharp-find-usages () 264 | "Find usages for the symbol under point" 265 | (interactive) 266 | (let ((quickfixes (omnisharp-find-usages-worker 267 | (omnisharp--get-common-params)))) 268 | 269 | (if (equal 0 (length quickfixes)) 270 | (message "No usages found.") 271 | 272 | (omnisharp--write-quickfixes-to-compilation-buffer 273 | quickfixes 274 | omnisharp--find-usages-buffer-name 275 | omnisharp-find-usages-header)))) 276 | 277 | (defun omnisharp-find-usages-worker (request) 278 | ;; TODO make this asyncronic like all other compilation processes! 279 | (let* ((quickfix-response (omnisharp-post-message-curl-as-json 280 | (concat (omnisharp-get-host) "findusages") 281 | request)) 282 | (quickfixes (omnisharp--vector-to-list 283 | (cdr (assoc 'QuickFixes quickfix-response))))) 284 | quickfixes)) 285 | 286 | (defun omnisharp-find-implementations () 287 | "Show a buffer containing all implementations of the interface under 288 | point, or classes derived from the class under point. Allow the user 289 | to select one (or more) to jump to." 290 | (interactive) 291 | (let ((quickfixes 292 | (omnisharp-find-implementations-worker 293 | (omnisharp--get-common-params)))) 294 | 295 | (omnisharp--write-quickfixes-to-compilation-buffer 296 | quickfixes 297 | omnisharp--find-implementations-buffer-name 298 | omnisharp-find-implementations-header))) 299 | 300 | (defun omnisharp-find-implementations-worker (request) 301 | "Returns a list of QuickFix lisp objects from a findimplementations 302 | api call made with the given Request." 303 | (let* ((quickfix-response (omnisharp-post-message-curl-as-json 304 | (concat (omnisharp-get-host) "findimplementations") 305 | request)) 306 | (quickfixes (omnisharp--vector-to-list 307 | (cdr (assoc 'QuickFixes quickfix-response))))) 308 | quickfixes)) 309 | 310 | (defun omnisharp-navigate-to-region 311 | (&optional other-window) 312 | "Navigate to region in current file. If OTHER-WINDOW is given and t, 313 | use another window." 314 | (interactive "P") 315 | (let ((quickfix-response 316 | (omnisharp-post-message-curl-as-json 317 | (concat (omnisharp-get-host) "gotoregion") 318 | (omnisharp--get-common-params)))) 319 | (omnisharp--choose-and-go-to-quickfix-ido 320 | (cdr (assoc 'QuickFixes quickfix-response)) 321 | other-window))) 322 | 323 | (defun omnisharp-rename () 324 | "Rename the current symbol to a new name. Lets the user choose what 325 | name to rename to, defaulting to the current name of the symbol." 326 | (interactive) 327 | (let* ((current-word (thing-at-point 'symbol)) 328 | (rename-to (read-string "Rename to: " current-word)) 329 | (rename-request 330 | (cons `(RenameTo . ,rename-to) 331 | (omnisharp--get-common-params))) 332 | 333 | (modified-file-responses 334 | (omnisharp-rename-worker rename-request)) 335 | (location-before-rename 336 | (omnisharp--get-common-params-for-emacs-side-use))) 337 | 338 | ;; save-excursion does not work here for some reason. 339 | 340 | ;; Set all modified files' contents to what the server thinks they 341 | ;; now contain. Doing this will make the user see the results of 342 | ;; the rename. 343 | (--each modified-file-responses 344 | (omnisharp--set-buffer-contents-to 345 | (cdr (assoc 'FileName it)) 346 | (cdr (assoc 'Buffer it)))) 347 | 348 | ;; Keep point in the buffer that initialized the rename so that 349 | ;; the user deos not feel disoriented 350 | (omnisharp-go-to-file-line-and-column location-before-rename) 351 | 352 | (message "Rename complete in files: %s" 353 | (--map (cdr (assoc 'FileName it)) 354 | modified-file-responses)))) 355 | 356 | (defun omnisharp-rename-worker (rename-request) 357 | "Given a RenameRequest, returns a list of ModifiedFileResponse 358 | objects." 359 | (let* ((rename-responses 360 | (omnisharp-post-message-curl-as-json 361 | (concat (omnisharp-get-host) "rename") 362 | rename-request)) 363 | (modified-files (omnisharp--vector-to-list 364 | (cdr (assoc 'Changes rename-responses))))) 365 | modified-files)) 366 | 367 | (defun omnisharp-rename-interactively () 368 | "Rename the current symbol to a new name. Lets the user choose what 369 | name to rename to, defaulting to the current name of the symbol. Any 370 | renames require interactive confirmation from the user." 371 | (interactive) 372 | (let* ((current-word (thing-at-point 'symbol)) 373 | (rename-to (read-string "Rename to: " current-word)) 374 | (delimited 375 | (y-or-n-p "Only rename full words?")) 376 | (all-solution-files 377 | (omnisharp--get-solution-files-list-of-strings)) 378 | (location-before-rename 379 | (omnisharp--get-common-params-for-emacs-side-use))) 380 | (tags-query-replace current-word 381 | rename-to 382 | delimited 383 | ;; This is expected to be a form that will be 384 | ;; evaluated to get the list of all files to 385 | ;; process. 386 | 'all-solution-files) 387 | ;; Keep point in the buffer that initialized the rename so that 388 | ;; the user deos not feel disoriented 389 | (omnisharp-go-to-file-line-and-column location-before-rename))) 390 | 391 | (defun omnisharp--write-quickfixes-to-compilation-buffer 392 | (quickfixes 393 | buffer-name 394 | buffer-header 395 | &optional dont-save-old-pos) 396 | "Takes a list of QuickFix objects and writes them to the 397 | compilation buffer with HEADER as its header. Shows the buffer 398 | when finished. 399 | 400 | If DONT-SAVE-OLD-POS is specified, will not save current position to 401 | find-tag-marker-ring. This is so this function may be used without 402 | messing with the ring." 403 | (let ((output-in-compilation-mode-format 404 | (mapcar 405 | 'omnisharp--find-usages-output-to-compilation-output 406 | quickfixes))) 407 | 408 | (omnisharp--write-lines-to-compilation-buffer 409 | output-in-compilation-mode-format 410 | (get-buffer-create buffer-name) 411 | buffer-header) 412 | (unless dont-save-old-pos 413 | (ring-insert find-tag-marker-ring (point-marker)) 414 | (omnisharp--show-last-buffer-position-saved-message 415 | (buffer-file-name))))) 416 | 417 | (defun omnisharp--write-lines-to-compilation-buffer 418 | (lines-to-write buffer-to-write-to &optional header) 419 | "Writes the given lines to the given buffer, and sets 420 | compilation-mode on. The contents of the buffer are erased. The 421 | buffer is marked read-only after inserting all lines. 422 | 423 | LINES-TO-WRITE are the lines to write, as-is. 424 | 425 | If HEADER is given, that is written to the top of the buffer. 426 | 427 | Expects the lines to be in a format that compilation-mode 428 | recognizes, so that the user may jump to the results." 429 | (with-current-buffer buffer-to-write-to 430 | (let ((inhibit-read-only t)) 431 | ;; read-only-mode new in Emacs 24.3 432 | (if (fboundp 'read-only-mode) 433 | (read-only-mode nil) 434 | (setq buffer-read-only nil)) 435 | (erase-buffer) 436 | 437 | (when (not (null header)) 438 | (insert header)) 439 | 440 | (mapc (lambda (element) 441 | (insert element) 442 | (insert "\n")) 443 | lines-to-write) 444 | (compilation-mode) 445 | (if (fboundp 'read-only-mode) 446 | (read-only-mode t) 447 | (setq buffer-read-only t)) 448 | (display-buffer buffer-to-write-to)))) 449 | 450 | (defun omnisharp--find-usages-output-to-compilation-output 451 | (json-result-single-element) 452 | "Converts a single element of a /findusages JSON response to a 453 | format that the compilation major mode understands and lets the user 454 | follow results to the locations in the actual files." 455 | (let ((filename (cdr (assoc 'FileName json-result-single-element))) 456 | (text (cdr (assoc 'Text json-result-single-element))) 457 | (line (cdr (assoc 'Line json-result-single-element))) 458 | (column (cdr (assoc 'Column json-result-single-element))) 459 | (text (cdr (assoc 'Text json-result-single-element)))) 460 | (concat filename 461 | ":" 462 | (prin1-to-string line) 463 | ":" 464 | (prin1-to-string column) 465 | ": \n" 466 | text 467 | "\n"))) 468 | 469 | (defun omnisharp-stop-server () 470 | "Stop the current omnisharp instance." 471 | (interactive) 472 | (omnisharp-post-message-curl 473 | (concat (omnisharp-get-host) "stopserver") 474 | nil)) 475 | 476 | ;; TODO create omnisharp-add-to-solution that lets user choose which 477 | ;; file to add. 478 | (defun omnisharp-add-to-solution-current-file () 479 | (interactive) 480 | (let ((params (omnisharp--get-common-params))) 481 | (omnisharp-add-to-solution-worker params) 482 | (message "Added %s to the solution." 483 | (cdr (assoc 'FileName params))))) 484 | 485 | (defun omnisharp-add-to-solution-dired-selected-files () 486 | "Add the files currently selected in dired to the current solution." 487 | (interactive) 488 | (let ((selected-files (dired-get-marked-files))) 489 | (--each selected-files 490 | (let ((params 491 | (cons `(FileName . ,it) 492 | (omnisharp--get-common-params)))) 493 | (omnisharp-add-to-solution-worker params)) 494 | (message "Added %s files to the solution." 495 | (prin1-to-string (length selected-files)))))) 496 | 497 | (defun omnisharp-add-to-solution-worker (params) 498 | "TODO" 499 | ;; TODO report results somehow 500 | (omnisharp-post-message-curl 501 | (concat (omnisharp-get-host) "addtoproject") 502 | params)) 503 | 504 | (defun omnisharp-remove-from-project-current-file () 505 | (interactive) 506 | (let ((params (omnisharp--get-common-params))) 507 | (omnisharp-remove-from-project-current-file-worker params) 508 | (message "Removed %s from the solution." 509 | (cdr (assoc 'FileName params))))) 510 | 511 | (defun omnisharp-remove-from-project-dired-selected-files () 512 | "Remove the files currently selected in dired from the current 513 | solution." 514 | (interactive) 515 | (let ((selected-files (dired-get-marked-files))) 516 | (--each selected-files 517 | (let ((params 518 | (cons `(FileName . ,it) 519 | (omnisharp--get-common-params)))) 520 | (omnisharp-remove-from-project-current-file-worker params)) 521 | (message "Removed %s files from the project." 522 | (prin1-to-string (length selected-files)))))) 523 | 524 | (defun omnisharp-remove-from-project-current-file-worker (params) 525 | (omnisharp-post-message-curl 526 | (concat (omnisharp-get-host) "removefromproject") 527 | params)) 528 | 529 | (defun omnisharp-add-reference () 530 | (interactive) 531 | (let* ((path-to-ref-file-to-add 532 | (ido-read-file-name "Add reference to (dll / project): " 533 | nil ;; start in current dir 534 | nil ;; no default filename 535 | t ;; only allow existing files 536 | 537 | ;; TODO use a predicate for filtering 538 | ;; dll and csproj files 539 | )) 540 | (tmp-params (omnisharp--get-common-params)) 541 | (params (add-to-list 'tmp-params 542 | `(Reference . ,path-to-ref-file-to-add)))) 543 | (omnisharp-add-reference-worker params))) 544 | 545 | (defun omnisharp-add-reference-worker (params) 546 | (omnisharp-post-message-curl-as-json 547 | (concat (omnisharp-get-host) "addreference") 548 | params)) 549 | 550 | (defun omnisharp-auto-complete () 551 | (interactive) 552 | (let* ((json-false :json-false) 553 | ;; json-false helps distinguish between null and false in 554 | ;; json. This is an emacs limitation. 555 | 556 | (params 557 | (omnisharp--get-auto-complete-params)) 558 | 559 | (display-function 560 | (omnisharp--get-auto-complete-display-function)) 561 | 562 | (json-result-auto-complete-response 563 | (omnisharp-auto-complete-worker params))) 564 | 565 | (funcall display-function json-result-auto-complete-response))) 566 | 567 | (defun omnisharp-add-dot-and-auto-complete () 568 | "Adds a . character and calls omnisharp-auto-complete. Meant to be 569 | bound to the dot key so pressing dot will automatically insert a dot 570 | and complete members." 571 | (interactive) 572 | (insert ".") 573 | (omnisharp-auto-complete)) 574 | 575 | (defun omnisharp--get-auto-complete-params () 576 | "Return an AutoCompleteRequest for the current buffer state." 577 | (let* ((request (omnisharp--get-common-params)) 578 | (want-doc (if (equal 579 | omnisharp-auto-complete-want-documentation 580 | nil) 581 | :json-false 582 | omnisharp-auto-complete-want-documentation)) 583 | (request-with-doc-option 584 | (cons 585 | `(WantDocumentationForEveryCompletionResult 586 | . ,want-doc) 587 | (omnisharp--get-common-params))) 588 | (final-request 589 | ;; Add WordToComplete to params 590 | (cons `(WordToComplete . ,(thing-at-point 'symbol)) 591 | request-with-doc-option))) 592 | final-request)) 593 | 594 | ;; Use this source in your csharp editing mode hook like so: 595 | ;; (add-to-list 'ac-sources 'ac-source-omnisharp) 596 | ;; 597 | ;; Unfortunately there seems to be a limit in the auto-complete 598 | ;; library that disallows camel case completions and such fancy 599 | ;; completions useless. 600 | 601 | ;; The library only seems to accept completions that have the same 602 | ;; leading characters as results. Oh well. 603 | (ac-define-source omnisharp 604 | '((candidates . omnisharp--get-auto-complete-result-in-popup-format))) 605 | 606 | (defun omnisharp--get-auto-complete-result-in-popup-format () 607 | "Returns /autocomplete API results \(autocompletions\) as popup 608 | items." 609 | (let* ((json-result-auto-complete-response 610 | (omnisharp-auto-complete-worker 611 | (omnisharp--get-auto-complete-params))) 612 | (completions-in-popup-format 613 | (omnisharp--convert-auto-complete-json-to-popup-format 614 | json-result-auto-complete-response))) 615 | completions-in-popup-format)) 616 | 617 | 618 | ;; company-mode integration 619 | (defvar omnisharp-company-do-template-completion t 620 | "Set to t if you want in-line parameter completion, nil 621 | otherwise") 622 | 623 | (defvar omnisharp-company-type-separator " : " 624 | "The string used to visually seperate functions/variables from 625 | their types") 626 | 627 | (defvar omnisharp-company-begin-after-member-access t 628 | "If t, begin completion when pressing '.' after a class, object 629 | or namespace") 630 | 631 | (defvar omnisharp-imenu-support nil 632 | "If t, activate imenu integration. Defaults to nil.") 633 | 634 | (defun omnisharp-company--prefix () 635 | "Returns the symbol to complete. Also, if point is on a dot, 636 | triggers a completion immediately" 637 | (let ((symbol (company-grab-symbol))) 638 | (if symbol 639 | (if (and omnisharp-company-begin-after-member-access 640 | (save-excursion 641 | (forward-char (- (length symbol))) 642 | (looking-back "\\." (- (point) 2)))) 643 | (cons symbol t) 644 | symbol) 645 | 'stop))) 646 | 647 | (defun company-omnisharp (command &optional arg &rest ignored) 648 | "Company-mode integration" 649 | (case command 650 | (prefix (and omnisharp-mode 651 | (not (company-in-string-or-comment)) 652 | (omnisharp-company--prefix))) 653 | 654 | (candidates (omnisharp--get-company-candidates arg)) 655 | 656 | ;; because "" doesn't return everything 657 | (no-cache (equal arg "")) 658 | 659 | (crop (when (string-match "(" arg) 660 | (substring arg 0 (match-beginning 0)))) 661 | 662 | (meta (omnisharp--get-company-candidate-data arg 'DisplayText)) 663 | 664 | (doc-buffer (let((doc-buffer (company-doc-buffer (omnisharp--get-company-candidate-data arg 'Description)))) 665 | (with-current-buffer doc-buffer 666 | (visual-line-mode)) 667 | doc-buffer)) 668 | 669 | 670 | (post-completion (let* ((end (point-marker)) 671 | (beg (- (point) (length arg)))) 672 | (if omnisharp-company-do-template-completion 673 | ;;If this was a function match, do templating 674 | (if (string-match "([^)]" arg) 675 | (company-template-c-like-templatify arg) 676 | ;;Otherwise, look for the type seperator and strip that off the end 677 | (if (string-match omnisharp-company-type-separator arg) 678 | (when (re-search-backward omnisharp-company-type-separator beg t) 679 | (delete-region (match-beginning 0) end)))) 680 | ;;If we aren't doing templating, string away anything after the ( 681 | ;; or anything after the type separator, if we don't find that. 682 | (if (string-match "(" arg) 683 | (when (re-search-backward "(" beg t) 684 | (delete-region (match-beginning 0) end)) 685 | ;; (forward-char)) 686 | (if (string-match omnisharp-company-type-separator arg) 687 | (when (re-search-backward omnisharp-company-type-separator beg t) 688 | (delete-region (match-beginning 0) end))))))))) 689 | 690 | 691 | 692 | (defun omnisharp--string-starts-with (s arg) 693 | "Returns non-nil if string S starts with ARG, else nil." 694 | (cond ((>= (length s) (length arg)) 695 | (string-equal (substring s 0 (length arg)) arg)) 696 | (t nil))) 697 | 698 | (defun omnisharp--filter-company-candidate (candidate-string element prefix) 699 | "Since company-mode expects the candidates to begin with the 700 | completion prefix, filter items that don't begin with the 701 | completion prefix. Also filter out completions that just match 702 | the prefix exactly, as they just confuse things" 703 | (if (and (not (string= (omnisharp--completion-result-item-get-completion-text element) prefix)) 704 | (omnisharp--string-starts-with candidate-string prefix)) 705 | candidate-string 706 | nil)) 707 | 708 | (defun omnisharp--make-company-completion-text (item) 709 | "company-mode expects the beginning of the candidate to be the 710 | same as the characters being completed. This method converts a 711 | function description of 'void SomeMethod(int parameter)' to 712 | 'SomeMethod(int parameter) : void'." 713 | (let* ((case-fold-search nil) 714 | (completion (omnisharp--completion-result-item-get-completion-text item)) 715 | (display (omnisharp--completion-result-item-get-display-text item)) 716 | (func-start-pos (string-match (concat " " completion) display)) 717 | (output display)) 718 | ;;If this candidate has a type, stick the return type on the end 719 | (if (and func-start-pos (> func-start-pos 0)) 720 | (progn 721 | (setq func-start-pos (+ func-start-pos 1)) 722 | (let ((func-return (substring display 0 func-start-pos)) 723 | (func-body (substring display func-start-pos))) 724 | (setq output (concat func-body omnisharp-company-type-separator func-return)))) 725 | (let ((brackets-start (string-match "()" display))) 726 | (when brackets-start 727 | (setq output (substring display 0 brackets-start))))) 728 | output)) 729 | 730 | (defun omnisharp--get-company-candidates (pre) 731 | "Returns completion results in company format. Company-mode 732 | doesn't make any distinction between the text to be inserted and 733 | the text to be displayed. As a result, since we want to see 734 | parameters and things, we need to munge 'DisplayText so it's 735 | company-mode-friendly" 736 | (let* ((json-false :json-false) 737 | ;; json-false helps distinguish between null and false in 738 | ;; json. This is an emacs limitation. 739 | (params 740 | (omnisharp--get-auto-complete-params)) 741 | 742 | (json-result-auto-complete-response 743 | (omnisharp-auto-complete-worker params)) 744 | (company-output (delq nil 745 | (mapcar 746 | (lambda (element) 747 | (omnisharp--filter-company-candidate (omnisharp--make-company-completion-text element) element pre)) 748 | json-result-auto-complete-response)))) 749 | company-output)) 750 | 751 | (defun omnisharp--get-company-candidate-data (pre datatype) 752 | "Given one of our completion candidate strings, find the 753 | element it matches and return the datatype request (e.g. 'DisplayText)." 754 | (interactive) 755 | (cl-loop for element across omnisharp--last-buffer-specific-auto-complete-result do 756 | (when (string-equal (omnisharp--make-company-completion-text element) pre) 757 | (cl-return (cdr (assoc datatype element)))))) 758 | 759 | ;;Add this completion backend to company-mode 760 | ;; (eval-after-load 'company 761 | ;; '(add-to-list 'company-backends 'company-omnisharp)) 762 | 763 | 764 | 765 | (defun omnisharp--get-auto-complete-display-function () 766 | "Returns a function that can be fed the output from 767 | omnisharp-auto-complete-worker - the AutoCompleteResponse JSON output 768 | from the omnisharp /autocomplete API. 769 | 770 | This function must know how to convert the raw JSON into a format that 771 | the user can choose one completion out of. Then that function must 772 | handle inserting that result in the way it sees fit (e.g. in the 773 | current buffer)." 774 | (cdr (assoc omnisharp--auto-complete-display-backend 775 | omnisharp--auto-complete-display-backends-alist))) 776 | 777 | (defun omnisharp--get-last-auto-complete-result-display-function () 778 | "Returns a function that can be fed the output from 779 | omnisharp-auto-complete-worker (an AutoCompleteResponse). The function 780 | must take a single argument, the auto-complete result texts to show." 781 | (cdr (assoc omnisharp--show-last-auto-complete-result-frontend 782 | omnisharp--show-last-auto-complete-result-frontends-alist))) 783 | 784 | (defun omnisharp-auto-complete-worker (auto-complete-request) 785 | "Takes an AutoCompleteRequest and makes an autocomplete query with 786 | them. 787 | 788 | Returns the raw JSON result. Also caches that result as 789 | omnisharp--last-buffer-specific-auto-complete-result." 790 | (let ((json-result 791 | (omnisharp-post-message-curl-as-json 792 | (concat (omnisharp-get-host) "autocomplete") 793 | auto-complete-request))) 794 | ;; Cache result so it may be juggled in different contexts easily 795 | (setq omnisharp--last-buffer-specific-auto-complete-result 796 | json-result))) 797 | 798 | (defun omnisharp-auto-complete-overrides () 799 | (interactive) 800 | (let ((params (omnisharp--get-common-params))) 801 | (omnisharp-auto-complete-overrides-worker params))) 802 | 803 | (defun omnisharp-auto-complete-overrides-worker (params) 804 | (let* ((json-result 805 | (omnisharp--vector-to-list 806 | (omnisharp-post-message-curl-as-json 807 | (concat (omnisharp-get-host) "getoverridetargets") 808 | params))) 809 | (target-names 810 | (mapcar (lambda (a) 811 | (cdr (assoc 'OverrideTargetName a))) 812 | json-result)) 813 | (chosen-override (ido-completing-read 814 | "Override: " 815 | target-names 816 | t))) 817 | (omnisharp-auto-complete-overrides-run-override 818 | chosen-override))) 819 | 820 | (defun omnisharp-auto-complete-overrides-run-override (override-name) 821 | (omnisharp-auto-complete-overrides-run-override-worker 822 | (cons `(OverrideTargetName . ,override-name) 823 | (omnisharp--get-common-params)))) 824 | 825 | (defun omnisharp-auto-complete-overrides-run-override-worker (params) 826 | (let ((json-result (omnisharp-post-message-curl-as-json 827 | (concat (omnisharp-get-host) "runoverridetarget") 828 | params))) 829 | (omnisharp--set-buffer-contents-to 830 | (cdr (assoc 'FileName json-result)) 831 | (cdr (assoc 'Buffer json-result)) 832 | (cdr (assoc 'Line json-result)) 833 | (cdr (assoc 'Column json-result))))) 834 | 835 | (defun omnisharp-run-code-action-refactoring () 836 | "Gets a list of refactoring code actions for the current editor 837 | position and file from the API. Asks the user what kind of refactoring 838 | they want to run. Then runs the action. 839 | 840 | Saves the current file before doing anything else. This is so that the 841 | user is less likely to lose data." 842 | 843 | (interactive) 844 | (save-buffer) 845 | (let* ((actions-vector (omnisharp--get-code-actions-from-api)) 846 | ;; CodeActions is a vector. Need to convert it to a list. 847 | (actions-list 848 | (omnisharp--vector-to-list 849 | (cdr (assoc 'CodeActions actions-vector))))) 850 | ;; Exit early if no refactorings are provided by the API. 851 | (if (<= (length actions-list) 0) 852 | (message "No refactorings available at this position.") 853 | 854 | (progn 855 | (let* ((chosen-action (ido-completing-read 856 | "Run code action: " 857 | actions-list 858 | t)) 859 | (chosen-action-index 860 | (position chosen-action actions-list))) 861 | 862 | (omnisharp-run-code-action-refactoring-worker 863 | chosen-action-index)))))) 864 | 865 | (defun omnisharp--get-code-actions-from-api () 866 | "Fetches and returns a GetCodeActionsResponse: the runnable 867 | refactoring code actions for the current file and position." 868 | (omnisharp-post-message-curl-as-json 869 | (concat (omnisharp-get-host) "getcodeactions") 870 | (omnisharp--get-common-params))) 871 | 872 | (defun omnisharp-run-code-action-refactoring-worker 873 | (chosen-action-index) 874 | 875 | (let* ((run-action-params 876 | (cons `(CodeAction . ,chosen-action-index) 877 | (omnisharp--get-common-params))) 878 | (json-run-action-result ; RunCodeActionsResponse 879 | (omnisharp-post-message-curl-as-json 880 | (concat (omnisharp-get-host) "runcodeaction") 881 | run-action-params))) 882 | 883 | (omnisharp-run-code-action-worker run-action-params 884 | json-run-action-result))) 885 | 886 | (defun omnisharp-run-code-action-worker (run-action-params 887 | json-run-action-result) 888 | "Gets new file contents with the chosen refactoring 889 | applied. Attempts to keep point still. 890 | 891 | run-action-params: original parameters sent to /runcodeaction API." 892 | (omnisharp--set-buffer-contents-to 893 | (buffer-file-name) 894 | (cdr (assoc 'Text json-run-action-result)) 895 | (line-number-at-pos) 896 | (omnisharp--current-column))) 897 | 898 | (defun omnisharp--set-buffer-contents-to (filename-for-buffer 899 | new-buffer-contents 900 | &optional 901 | result-point-line 902 | result-point-column) 903 | "Sets the buffer contents to new-buffer-contents for the buffer 904 | visiting filename-for-buffer. If no buffer is visiting that file, does 905 | nothing. Afterwards moves point to the coordinates RESULT-POINT-LINE 906 | and RESULT-POINT-COLUMN. 907 | 908 | If RESULT-POINT-LINE and RESULT-POINT-COLUMN are not given, and a 909 | buffer exists for FILENAME-FOR-BUFFER, its current positions are 910 | used. If a buffer does not exist, the file is visited and the default 911 | point position is used." 912 | (omnisharp--find-file-possibly-in-other-window 913 | filename-for-buffer nil) ; not in other-window 914 | 915 | ;; Default values are the ones in the buffer that is visiting 916 | ;; filename-for-buffer. 917 | (setq result-point-line 918 | (or result-point-line (line-number-at-pos))) 919 | (setq result-point-column 920 | (or result-point-column (omnisharp--current-column))) 921 | 922 | (save-buffer) 923 | (erase-buffer) 924 | (insert new-buffer-contents) 925 | 926 | ;; Hack. Puts point where it belongs. 927 | (omnisharp-go-to-file-line-and-column-worker 928 | result-point-line result-point-column filename-for-buffer)) 929 | 930 | (defun omnisharp--current-column () 931 | "Returns the current column, converting tab characters in a way that 932 | the OmniSharp server understands." 933 | (let ((tab-width 1)) 934 | (current-column))) 935 | 936 | (defun omnisharp--buffer-exists-for-file-name (file-name) 937 | (let ((all-open-buffers-list 938 | (-map 'buffer-file-name (buffer-list)))) 939 | (--any? (string-equal file-name it) 940 | all-open-buffers-list))) 941 | 942 | (defun omnisharp--convert-slashes-to-double-backslashes (str) 943 | "This might be useful. A direct port from OmniSharp.py." 944 | (replace-regexp-in-string "/" "\\\\" str)) 945 | 946 | (defun omnisharp--get-current-buffer-contents () 947 | (buffer-substring-no-properties (buffer-end 0) (buffer-end 1))) 948 | 949 | (defun omnisharp-post-message-curl (url params) 950 | "Post json stuff to url with --data set to given params. Return 951 | result." 952 | (let ((curl-command-plist 953 | (omnisharp--get-curl-command url params))) 954 | (with-temp-buffer 955 | (apply 'call-process 956 | (plist-get curl-command-plist :command) 957 | nil ;; infile 958 | (buffer-name);; destination 959 | nil ;; display (no specialities needed) 960 | ;; these are just args 961 | (plist-get curl-command-plist :arguments)) 962 | (buffer-string)))) 963 | 964 | (defun omnisharp--get-curl-command (url params) 965 | "Returns a command that may be used to communicate with the API via 966 | the curl program. Depends on the operating system." 967 | (if (equal system-type 'windows-nt) 968 | (omnisharp--get-curl-command-windows-with-tmp-file url params) 969 | (omnisharp--get-curl-command-unix url params))) 970 | 971 | (defun omnisharp--get-curl-command-unix (url params) 972 | "Returns a command using plain curl that can be executed to 973 | communicate with the API." 974 | `(:command "curl" 975 | :arguments 976 | ("--silent" "-H" "Content-type: application/json" 977 | "--data" 978 | ,(json-encode params) 979 | ,url))) 980 | 981 | (defun omnisharp--get-curl-command-windows-with-tmp-file (url params) 982 | "Basically: put PARAMS to file, then create a curl command to the 983 | api at URL using that file as the parameters." 984 | ;; TODO could optimise: short buffers need not be written to tmp 985 | ;; files. 986 | (omnisharp--write-json-params-to-tmp-file 987 | omnisharp--windows-curl-tmp-file-path 988 | (json-encode params)) 989 | (let ((path-with-curl-prefix 990 | (concat "@" 991 | omnisharp--windows-curl-tmp-file-path))) 992 | `(:command "curl" 993 | :arguments 994 | ("--silent" "-H" "Content-type: application/json" 995 | "--data-binary" 996 | ;; @ specifies a file path to curl 997 | ,path-with-curl-prefix 998 | ,url)))) 999 | 1000 | (defun omnisharp--write-json-params-to-tmp-file 1001 | (target-path stuff-to-write-to-file) 1002 | "Deletes the file when done." 1003 | (with-temp-file target-path 1004 | (insert stuff-to-write-to-file))) 1005 | 1006 | (defun omnisharp-post-message-curl-as-json (url params) 1007 | (json-read-from-string 1008 | (omnisharp-post-message-curl url params))) 1009 | 1010 | (defun omnisharp--auto-complete-display-function-popup 1011 | (json-result-alist) 1012 | "Gets an association list such as this: 1013 | (((DisplayText . \"Gender\") 1014 | (Description . \"int Gender { get; set; }\") 1015 | (CompletionText . \"Gender\"))) 1016 | 1017 | Displays a popup.el popup menu, and inserts the chosen element in the 1018 | current buffer." 1019 | (if (equalp 0 (length json-result-alist)) 1020 | (progn (message "No completions.") 1021 | nil) 1022 | 1023 | (let* ((display-list 1024 | (omnisharp--convert-auto-complete-json-to-popup-format 1025 | json-result-alist)) 1026 | 1027 | (completion-texts 1028 | (mapcar 'omnisharp--completion-result-item-get-display-text 1029 | json-result-alist)) 1030 | 1031 | (max-width (omnisharp--get-max-item-length 1032 | completion-texts)) 1033 | (result (popup-menu* display-list 1034 | :width max-width 1035 | :keymap omnisharp-auto-complete-popup-keymap 1036 | :margin-left 1 1037 | :margin-right 1 1038 | :scroll-bar t 1039 | :isearch 1040 | omnisharp-auto-complete-popup-want-isearch 1041 | :help-delay 1042 | omnisharp-auto-complete-popup-help-delay))) 1043 | (omnisharp--replace-symbol-in-buffer-with 1044 | (omnisharp--current-word-or-empty-string) 1045 | result)))) 1046 | 1047 | (defun omnisharp--replace-symbol-in-buffer-with (symbol-to-replace 1048 | replacement-string) 1049 | "In the current buffer, replaces the given SYMBOL-TO-REPLACE 1050 | \(a string\) with REPLACEMENT-STRING." 1051 | (search-backward symbol-to-replace) 1052 | (replace-match replacement-string t t)) 1053 | 1054 | (defun omnisharp--auto-complete-display-function-ido 1055 | (json-result-alist) 1056 | "Use ido style completion matching with autocomplete candidates. Ido 1057 | is a more sophisticated matching framework than what popup.el offers." 1058 | 1059 | (if (equalp 0 (length json-result-alist)) 1060 | (progn (message "No completions.") 1061 | nil) 1062 | 1063 | (let* ((candidates (omnisharp--vector-to-list json-result-alist)) 1064 | 1065 | (display-texts 1066 | (mapcar 'omnisharp--completion-result-item-get-display-text 1067 | candidates)) 1068 | 1069 | ;; This is only the display text. The text to be inserted 1070 | ;; in the buffer will be fetched with this 1071 | ;; 1072 | ;; TODO does ido-completing-read allow a custom format that 1073 | ;; could store these, as with popup-make-item ? 1074 | (user-chosen-display-text 1075 | (ido-completing-read 1076 | "Complete: " 1077 | display-texts)) 1078 | 1079 | ;; Get the chosen candidate by getting the index of the 1080 | ;; chosen DisplayText. The candidate with the same index is 1081 | ;; the one we want. 1082 | (json-result-element-index-with-user-chosen-text 1083 | (position-if (lambda (element) 1084 | (equal element 1085 | user-chosen-display-text)) 1086 | display-texts)) 1087 | (chosen-candidate 1088 | (nth json-result-element-index-with-user-chosen-text 1089 | candidates)) 1090 | 1091 | (completion-text-to-insert 1092 | (cdr (assoc 'CompletionText 1093 | chosen-candidate)))) 1094 | (omnisharp--replace-symbol-in-buffer-with 1095 | (omnisharp--current-word-or-empty-string) 1096 | completion-text-to-insert)))) 1097 | 1098 | (defun omnisharp--current-word-or-empty-string () 1099 | (or (thing-at-point 'symbol) 1100 | "")) 1101 | 1102 | ;; TODO Use a plist. This is ridiculous. 1103 | (defun omnisharp--convert-auto-complete-json-to-popup-format 1104 | (json-result-alist) 1105 | (mapcar 1106 | (lambda (element) 1107 | (popup-make-item 1108 | ;; TODO get item from json-result-alist 1109 | ;; 1110 | ;; TODO these are already calculated in 1111 | ;; omnisharp--auto-complete-display-function-popup, stored as 1112 | ;; completion-texts 1113 | (cdr (assoc 'DisplayText element)) 1114 | :value (omnisharp--completion-result-item-get-completion-text 1115 | element) 1116 | :document (cdr (assoc 'Description element)))) 1117 | json-result-alist)) 1118 | 1119 | (defun omnisharp--completion-result-item-get-completion-text (item) 1120 | (cdr (assoc 'CompletionText item))) 1121 | 1122 | (defun omnisharp--completion-result-item-get-display-text (item) 1123 | (cdr (assoc 'DisplayText item))) 1124 | 1125 | (defun omnisharp--get-max-item-length (completions) 1126 | "Returns the length of the longest completion in 'completions'." 1127 | (if (null completions) 1128 | 0 1129 | (reduce 'max (mapcar 'length completions)))) 1130 | 1131 | (defun omnisharp--get-common-params () 1132 | "Get common parameters used in the base request class Request." 1133 | (let* ((line-number (number-to-string (line-number-at-pos))) 1134 | (column-number (number-to-string (+ 1 (omnisharp--current-column)))) 1135 | (buffer-contents (omnisharp--get-current-buffer-contents)) 1136 | (filename-tmp (omnisharp--convert-slashes-to-double-backslashes 1137 | (or buffer-file-name ""))) 1138 | (params `((Line . ,line-number) 1139 | (Column . ,column-number) 1140 | (Buffer . ,buffer-contents)))) 1141 | (if (/= 0 (length filename-tmp)) 1142 | (cons `(FileName . ,filename-tmp) 1143 | params) 1144 | params))) 1145 | 1146 | (defun omnisharp--get-common-params-for-emacs-side-use () 1147 | "Gets a Request class that can be only handled safely inside 1148 | Emacs. This should not be transferred to the server backend - it might 1149 | not work on all platforms." 1150 | (let* ((line-number (line-number-at-pos)) 1151 | (column-number (omnisharp--current-column)) 1152 | (buffer-contents (omnisharp--get-current-buffer-contents)) 1153 | (filename-tmp (or buffer-file-name "")) 1154 | (params `((Line . ,line-number) 1155 | (Column . ,column-number) 1156 | (Buffer . ,buffer-contents)))) 1157 | (if (/= 0 (length filename-tmp)) 1158 | (cons `(FileName . ,filename-tmp) 1159 | params) 1160 | params))) 1161 | 1162 | (defun omnisharp-go-to-file-line-and-column (json-result 1163 | &optional other-window) 1164 | "Open file :FileName at :Line and :Column. If filename is not given, 1165 | defaults to the current file. This function works for a 1166 | QuickFix class json result." 1167 | (omnisharp-go-to-file-line-and-column-worker 1168 | (cdr (assoc 'Line json-result)) 1169 | (- (cdr (assoc 'Column json-result)) 1) 1170 | (cdr (assoc 'FileName json-result)) 1171 | other-window)) 1172 | 1173 | (defun omnisharp-go-to-file-line-and-column-worker (line 1174 | column 1175 | &optional filename 1176 | other-window 1177 | dont-save-old-pos) 1178 | "Open file filename at line and column. If filename is not given, 1179 | defaults to the current file. Saves the current location into the tag 1180 | ring so that the user may return with (pop-tag-mark). 1181 | 1182 | If DONT-SAVE-OLD-POS is specified, will not save current position to 1183 | find-tag-marker-ring. This is so this function may be used without 1184 | messing with the ring." 1185 | 1186 | (let ((position-before-jumping (point-marker))) 1187 | (when filename 1188 | (omnisharp--find-file-possibly-in-other-window filename 1189 | other-window)) 1190 | 1191 | ;; calling goto-line directly results in a compiler warning. 1192 | (with-no-warnings 1193 | (goto-line line)) 1194 | 1195 | (move-to-column column) 1196 | 1197 | (unless dont-save-old-pos 1198 | (omnisharp--save-position-to-find-tag-marker-ring 1199 | position-before-jumping) 1200 | (omnisharp--show-last-buffer-position-saved-message 1201 | (buffer-file-name 1202 | (marker-buffer position-before-jumping)))))) 1203 | 1204 | (defun omnisharp--show-last-buffer-position-saved-message 1205 | (&optional file-name) 1206 | "Notifies the user that the previous buffer position has been saved 1207 | with a message in the minibuffer. If FILE-NAME is given, shows that as 1208 | the file. Otherwise uses the current file name." 1209 | (message "Previous position in %s saved. Go back with (pop-tag-mark)." 1210 | (or file-name 1211 | (buffer-file-name)))) 1212 | 1213 | (defun omnisharp--save-position-to-find-tag-marker-ring 1214 | (&optional marker) 1215 | "Record position in find-tag-marker-ring. If MARKER is non-nil, 1216 | record that position. Otherwise record the current position." 1217 | (setq marker (or marker (point-marker))) 1218 | (ring-insert find-tag-marker-ring marker)) 1219 | 1220 | (defun omnisharp--find-file-possibly-in-other-window 1221 | (filename &optional other-window) 1222 | "Open a buffer editing FILENAME. If no buffer for that filename 1223 | exists, a new one is created. 1224 | If the optional argument OTHER-WINDOW is non-nil, uses another 1225 | window." 1226 | 1227 | (cond 1228 | ((omnisharp--buffer-exists-for-file-name filename) 1229 | (let ((target-buffer-to-switch-to 1230 | (--first (string= (buffer-file-name it) 1231 | filename) 1232 | (buffer-list)))) 1233 | (if other-window 1234 | (pop-to-buffer target-buffer-to-switch-to) 1235 | (pop-to-buffer-same-window target-buffer-to-switch-to)))) 1236 | 1237 | (t ; no buffer for this file exists yet 1238 | (funcall (if other-window 1239 | 'find-file-other-window 1240 | 'find-file) 1241 | filename)))) 1242 | 1243 | (defun omnisharp--vector-to-list (vector) 1244 | (append vector nil)) 1245 | 1246 | (defun omnisharp--popup-to-ido () 1247 | "When in a popup menu with autocomplete suggestions, calling this 1248 | function will close the popup and open an ido prompt instead. 1249 | 1250 | Note that currently this will leave the popup menu active even when 1251 | the user selects a completion and the completion is inserted." 1252 | 1253 | (interactive) ; required. Otherwise call to this is silently ignored 1254 | 1255 | ;; TODO how to check if popup is active? 1256 | (omnisharp--auto-complete-display-function-ido 1257 | omnisharp--last-buffer-specific-auto-complete-result)) 1258 | 1259 | (defun omnisharp-current-type-information (&optional add-to-kill-ring) 1260 | "Display information of the current type under point. With prefix 1261 | argument, add the displayed result to the kill ring. This can be used 1262 | to insert the result in code, for example." 1263 | (interactive "P") 1264 | (let ((current-type-information 1265 | (omnisharp-current-type-information-worker 1266 | (omnisharp--get-common-params)))) 1267 | 1268 | (message current-type-information) 1269 | (when add-to-kill-ring 1270 | (kill-new current-type-information)))) 1271 | 1272 | (defun omnisharp-current-type-information-worker (params) 1273 | "Returns information about the type under the cursor in the given 1274 | PARAMS as a single human-readable string." 1275 | (let ((json-result 1276 | (omnisharp-post-message-curl-as-json 1277 | (concat (omnisharp-get-host) "typelookup") 1278 | params))) 1279 | (cdr (assoc 'Type json-result)))) 1280 | 1281 | (defun omnisharp-current-type-information-to-kill-ring () 1282 | "Shows the information of the current type and adds it to the kill 1283 | ring." 1284 | (interactive) 1285 | (omnisharp-current-type-information t)) 1286 | 1287 | (defun omnisharp-get-build-command () 1288 | "Retrieve the shell command to build the current solution." 1289 | (omnisharp-post-message-curl 1290 | (concat (omnisharp-get-host) "buildcommand") 1291 | nil)) 1292 | 1293 | (defun omnisharp-build-in-emacs () 1294 | "Build the current solution in a non-blocking fashion inside emacs. 1295 | Uses the standard compilation interface (compile)." 1296 | (interactive) 1297 | (let ((build-command (omnisharp-get-build-command))) 1298 | (omnisharp--recognize-mono-compilation-error-format) 1299 | (compile 1300 | ;; Build command contains backslashes on Windows systems. Work 1301 | ;; around this by using double backslashes. Other systems are not 1302 | ;; affected. 1303 | (omnisharp--fix-build-command-if-on-windows 1304 | build-command)) 1305 | (add-to-list 'compile-history build-command))) 1306 | 1307 | (defun omnisharp--recognize-mono-compilation-error-format () 1308 | "Makes Emacs recognize the mono compiler errors as clickable 1309 | compilation buffer elements." 1310 | (add-to-list 'compilation-error-regexp-alist 1311 | '(" in \\(.+\\):\\([0-9]+\\)" 1 2))) 1312 | 1313 | (defun omnisharp--fix-build-command-if-on-windows (command) 1314 | "Fixes the build command gotten via omnisharp-get-build-command. 1315 | See function definition for an example. 1316 | 1317 | If not on windows, returns COMMAND unchanged." 1318 | 1319 | ;; Example input without fix: 1320 | ;; C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Msbuild.exe /m /nologo 1321 | ;; /v:q /property:GenerateFullPaths=true 1322 | ;; \"c:/Projects/foo/foo.sln\" 1323 | 1324 | ;; This changes that to this: 1325 | ;; C:/Windows/Microsoft.NET/Framework64/v4.0.30319/Msbuild.exe 1326 | ;; //m //nologo //v:q //property:GenerateFullPaths=true 1327 | ;; \"c://Projects//bowsville_freelancer//src//Bowsville.Freelancer.sln\" 1328 | ;; 1329 | ;; ^ this works :) 1330 | 1331 | 1332 | (if (equal system-type 'windows-nt) 1333 | ;; Compiler path fix. C:\Path is interpreted as C:Path 1334 | (omnisharp--convert-backslashes-to-forward-slashes 1335 | ;; Compiler parameter fix. Emacs thinks "/m" refers to the path 1336 | ;; /m - that is, (root)/m 1337 | (omnisharp--convert-slashes-to-double-slashes 1338 | command)) 1339 | 1340 | ;; Not on windows. Do not change. 1341 | command)) 1342 | 1343 | (defun omnisharp--convert-backslashes-to-forward-slashes 1344 | (string-to-convert) 1345 | "Converts the given STRING-TO-CONVERT's backslashes to forward 1346 | slashes." 1347 | (replace-regexp-in-string "\\\\" "/" string-to-convert)) 1348 | 1349 | (defun omnisharp--convert-slashes-to-double-slashes (command) 1350 | (replace-regexp-in-string "/" "//" command)) 1351 | 1352 | (defun omnisharp-code-format () 1353 | "Format the code in the current file. Replaces the file contents 1354 | with the formatted result. Saves the file before starting." 1355 | (interactive) 1356 | (save-buffer) 1357 | (omnisharp-code-format-worker 1358 | ;; Add omnisharp-code-format-expand-tab to params 1359 | (cons `(ExpandTab . ,omnisharp-code-format-expand-tab) 1360 | (omnisharp--get-common-params)) 1361 | (buffer-file-name) 1362 | (line-number-at-pos) 1363 | (omnisharp--current-column))) 1364 | 1365 | (defun omnisharp-code-format-worker (code-format-request 1366 | filename 1367 | current-line 1368 | current-column) 1369 | (let ((json-result 1370 | (omnisharp-post-message-curl-as-json 1371 | (concat (omnisharp-get-host) "codeformat") 1372 | code-format-request))) 1373 | (omnisharp--set-buffer-contents-to 1374 | filename 1375 | (cdr (assoc 'Buffer json-result)) 1376 | current-line 1377 | current-column))) 1378 | 1379 | ;; This currently has no UI, so there only exists the 1380 | ;; worker. Originally the plan was to be able to run manual syntax 1381 | ;; checks but I couldn't figure out how to call them with flycheck. 1382 | (defun omnisharp-syntax-check-worker (params) 1383 | "Takes a Request and returns a SyntaxErrorsResponse." 1384 | (omnisharp-post-message-curl-as-json 1385 | (concat (omnisharp-get-host) "syntaxerrors") 1386 | params)) 1387 | 1388 | (flycheck-define-checker csharp-omnisharp-curl 1389 | "A csharp source syntax checker using curl to call an OmniSharp 1390 | server process running in the background. Only checks the syntax - not 1391 | type errors." 1392 | ;; This must be an external process. Currently flycheck does not 1393 | ;; support using elisp functions as checkers. 1394 | :command ((eval 1395 | (let ((command-plist 1396 | (omnisharp--get-curl-command 1397 | (concat (omnisharp-get-host) "syntaxerrors") 1398 | (omnisharp--get-common-params)))) 1399 | (cons 1400 | (plist-get command-plist :command) 1401 | (plist-get command-plist :arguments))))) 1402 | 1403 | :error-patterns ((error line-start 1404 | (file-name) ":" 1405 | line ":" 1406 | column 1407 | " " 1408 | (message (one-or-more not-newline)))) 1409 | ;; TODO this should be cleaned, but I can't get it to compile that 1410 | ;; way. 1411 | :error-parser (lambda (output checker buffer) 1412 | (omnisharp--flycheck-error-parser-raw-json 1413 | output checker buffer)) 1414 | ;; TODO use only is csharp files - but there are a few different 1415 | ;; extensions available for these! 1416 | :predicate (lambda () t)) 1417 | 1418 | (defun omnisharp--flycheck-error-parser-raw-json 1419 | (output checker buffer) 1420 | (let* ((json-result 1421 | (json-read-from-string output)) 1422 | (errors (omnisharp--vector-to-list 1423 | (cdr (assoc 'Errors json-result))))) 1424 | (when (not (equal (length errors) 0)) 1425 | (mapcar (lambda (it) 1426 | (flycheck-error-new 1427 | :buffer buffer 1428 | :checker checker 1429 | :filename (cdr (assoc 'FileName it)) 1430 | :line (cdr (assoc 'Line it)) 1431 | :column (cdr (assoc 'Column it)) 1432 | :message (cdr (assoc 'Message it)) 1433 | :level 'error)) 1434 | errors)))) 1435 | 1436 | (defun omnisharp--imenu-make-marker (element) 1437 | "Takes a QuickCheck element and returns the position of the 1438 | cursor at that location" 1439 | (let* ((element-line (cdr (assoc 'Line quickfix-alist))) 1440 | (element-column (cdr (assoc 'Column quickfix-alist))) 1441 | (element-filename (cdr (assoc 'Filename quickfix-alist))) 1442 | (use-buffer (current-buffer))) 1443 | (save-excursion 1444 | (omnisharp-go-to-file-line-and-column-worker 1445 | element-line 1446 | element-column 1447 | element-filename 1448 | nil ; other-window 1449 | ;; dont-save-old-pos 1450 | t) 1451 | (point-marker)))) 1452 | 1453 | (defun omnisharp-imenu-create-index () 1454 | "Imenu callback function - returns an alist of ((member-name . position))" 1455 | (interactive) 1456 | (let* ((quickfixes (omnisharp-post-message-curl-as-json 1457 | (concat (omnisharp-get-host) "currentfilemembersasflat") 1458 | (omnisharp--get-common-params))) 1459 | (list-quickfixes (omnisharp--vector-to-list quickfixes)) 1460 | (imenu-list (mapcar (lambda (quickfix-alist) 1461 | (cons (cdr (assoc 'Text quickfix-alist)) 1462 | (omnisharp--imenu-make-marker quickfix-alist))) 1463 | list-quickfixes))) 1464 | imenu-list)) 1465 | 1466 | 1467 | (defun omnisharp-navigate-to-current-file-member 1468 | (&optional other-window) 1469 | "Show a list of all members in the current file, and jump to the 1470 | selected member. With prefix argument, use another window." 1471 | (interactive "P") 1472 | (omnisharp-navigate-to-current-file-member-worker 1473 | (omnisharp--get-common-params) 1474 | other-window)) 1475 | 1476 | (defun omnisharp-navigate-to-current-file-member-other-window () 1477 | (interactive) 1478 | (omnisharp-navigate-to-current-file-member t)) 1479 | 1480 | (defun omnisharp-navigate-to-current-file-member-worker 1481 | (request &optional other-window) 1482 | (let ((quickfixes (omnisharp-post-message-curl-as-json 1483 | (concat (omnisharp-get-host) "currentfilemembersasflat") 1484 | request))) 1485 | (omnisharp--choose-and-go-to-quickfix-ido 1486 | quickfixes 1487 | other-window))) 1488 | 1489 | (defun omnisharp--choose-and-go-to-quickfix-ido 1490 | (quickfixes &optional other-window) 1491 | "Given a list of QuickFixes in list format (not JSON), displays them 1492 | in an ido-completing-read prompt and jumps to the chosen one's 1493 | Location. 1494 | 1495 | If OTHER-WINDOW is given, will jump to the result in another window." 1496 | (let ((chosen-quickfix 1497 | (omnisharp--choose-quickfix-ido 1498 | (omnisharp--vector-to-list quickfixes)))) 1499 | (omnisharp-go-to-file-line-and-column chosen-quickfix 1500 | other-window))) 1501 | 1502 | (defun omnisharp--choose-quickfix-ido (quickfixes) 1503 | "Given a list of QuickFixes, lets the user choose one using 1504 | ido-completing-read. Returns the chosen element." 1505 | ;; Ido cannot navigate non-unique items reliably. It either gets 1506 | ;; stuck, or results in that we cannot reliably determine the index 1507 | ;; of the item. Work around this by prepending the index of all items 1508 | ;; to their end. This makes them unique. 1509 | (let* ((quickfix-choices 1510 | (--map-indexed 1511 | (let ((this-quickfix-text (cdr (assoc 'Text it)))) 1512 | (concat "#" 1513 | (number-to-string it-index) 1514 | "\t" 1515 | this-quickfix-text)) 1516 | 1517 | quickfixes)) 1518 | 1519 | (chosen-quickfix-text 1520 | (ido-completing-read 1521 | "Go to: " 1522 | ;; TODO use a hashmap if too slow. 1523 | ;; This algorithm is two iterations in the worst case 1524 | ;; scenario. 1525 | quickfix-choices)) 1526 | (chosen-quickfix-index 1527 | (position-if (lambda (quickfix-text) 1528 | (equal quickfix-text chosen-quickfix-text)) 1529 | quickfix-choices))) 1530 | (nth chosen-quickfix-index quickfixes))) 1531 | 1532 | (defun omnisharp-navigate-to-type-in-current-file () 1533 | (interactive) 1534 | (omnisharp-navigate-to-type-in-current-file-worker 1535 | (omnisharp--get-common-params))) 1536 | 1537 | (defun omnisharp-navigate-to-type-in-current-file-worker (request) 1538 | (let ((quickfixes 1539 | (omnisharp-post-message-curl-as-json 1540 | (concat (omnisharp-get-host) "currentfiletopleveltypes") 1541 | request))) 1542 | (omnisharp--choose-and-go-to-quickfix-ido 1543 | quickfixes))) 1544 | 1545 | ;; No need for a worker pattern since findsymbols takes no arguments 1546 | (defun omnisharp-navigate-to-solution-member 1547 | (&optional other-window) 1548 | (interactive "P") 1549 | (let ((quickfix-response 1550 | (omnisharp-post-message-curl-as-json 1551 | (concat (omnisharp-get-host) "findsymbols") 1552 | nil))) 1553 | (omnisharp--choose-and-go-to-quickfix-ido 1554 | (omnisharp--vector-to-list 1555 | (cdr (assoc 'QuickFixes quickfix-response))) 1556 | other-window))) 1557 | 1558 | (defun omnisharp-navigate-to-solution-member-other-window 1559 | (omnisharp-navigate-to-solution-member t)) 1560 | 1561 | (defun omnisharp-navigate-to-solution-file 1562 | (&optional other-window) 1563 | (interactive "P") 1564 | (let ((quickfix-response 1565 | (omnisharp--get-solution-files-quickfix-response))) 1566 | (omnisharp--choose-and-go-to-quickfix-ido 1567 | (omnisharp--vector-to-list 1568 | (cdr (assoc 'QuickFixes quickfix-response))) 1569 | other-window))) 1570 | 1571 | (defun omnisharp--get-solution-files-quickfix-response () 1572 | "Return a QuickFixResponse containing a list of all locations of 1573 | files in the current solution." 1574 | (omnisharp-post-message-curl-as-json 1575 | (concat (omnisharp-get-host) "gotofile") 1576 | nil)) 1577 | 1578 | (defun omnisharp--get-solution-files-list-of-strings () 1579 | "Returns all files in the current solution as a list of strings." 1580 | ;; This is just mapping functions one after another. Read from top 1581 | ;; to bottom. 1582 | (->> (omnisharp--get-solution-files-quickfix-response) 1583 | (assoc 'QuickFixes) 1584 | (cdr) 1585 | (omnisharp--vector-to-list) 1586 | (--map (cdr (assoc 'FileName it))))) 1587 | 1588 | (defun omnisharp-navigate-to-solution-file-then-file-member 1589 | (&optional other-window) 1590 | "Navigates to a file in the solution first, then to a member in that 1591 | file. With prefix argument uses another window." 1592 | (interactive "P") 1593 | (omnisharp-navigate-to-solution-file other-window) 1594 | ;; Do not set other-window here. No need to use two different 1595 | ;; windows. 1596 | (omnisharp-navigate-to-current-file-member)) 1597 | 1598 | (defun omnisharp-navigate-to-solution-file-then-file-member-other-window 1599 | (&optional other-window) 1600 | (omnisharp-navigate-to-solution-file-then-file-member t)) 1601 | 1602 | (defun omnisharp-navigate-to-region 1603 | (&optional other-window) 1604 | "Navigate to region in current file. If OTHER-WINDOW is given and t, 1605 | use another window." 1606 | (interactive "P") 1607 | (let ((quickfix-response 1608 | (omnisharp-post-message-curl-as-json 1609 | (concat (omnisharp-get-host) "gotoregion") 1610 | (omnisharp--get-common-params)))) 1611 | (omnisharp--choose-and-go-to-quickfix-ido 1612 | (cdr (assoc 'QuickFixes quickfix-response)) 1613 | other-window))) 1614 | 1615 | (defun omnisharp-navigate-to-region-other-window () 1616 | (interactive) (omnisharp-navigate-to-region t)) 1617 | 1618 | (defun omnisharp-start-flycheck () 1619 | "Selects and starts the csharp-omnisharp-curl syntax checker for the 1620 | current buffer. Use this in your csharp-mode hook." 1621 | (interactive) 1622 | (flycheck-mode) 1623 | (flycheck-select-checker 'csharp-omnisharp-curl) 1624 | (flycheck-start-checker 'csharp-omnisharp-curl)) 1625 | 1626 | (defun omnisharp-navigate-to-region () 1627 | (interactive) 1628 | (let ((quickfix-response 1629 | (omnisharp-post-message-curl-as-json 1630 | (concat (omnisharp-get-host) "gotoregion") 1631 | (omnisharp--get-common-params)))) 1632 | (omnisharp--choose-and-go-to-quickfix-ido 1633 | (cdr (assoc 'QuickFixes quickfix-response))))) 1634 | 1635 | (defun omnisharp-show-last-auto-complete-result () 1636 | (interactive) 1637 | (let ((auto-complete-result-in-human-readable-form 1638 | (--map (cdr (assoc 'DisplayText it)) 1639 | omnisharp--last-buffer-specific-auto-complete-result))) 1640 | (funcall (omnisharp--get-last-auto-complete-result-display-function) 1641 | auto-complete-result-in-human-readable-form))) 1642 | 1643 | (defun omnisharp--show-last-auto-complete-result-in-plain-buffer 1644 | (auto-complete-result-in-human-readable-form-list) 1645 | "Display function for omnisharp-show-last-auto-complete-result using 1646 | a simple 'compilation' like buffer to display the last auto-complete 1647 | result." 1648 | (let ((buffer 1649 | (get-buffer-create 1650 | omnisharp--last-auto-complete-result-buffer-name))) 1651 | (omnisharp--write-lines-to-compilation-buffer 1652 | auto-complete-result-in-human-readable-form-list 1653 | buffer 1654 | omnisharp--last-auto-complete-result-buffer-header))) 1655 | 1656 | (defun omnisharp-show-overloads-at-point () 1657 | (interactive) 1658 | ;; Request completions from API but only cache them - don't show the 1659 | ;; results to the user 1660 | (save-excursion 1661 | (end-of-thing 'symbol) 1662 | (omnisharp-auto-complete-worker 1663 | (omnisharp--get-auto-complete-params)) 1664 | (omnisharp-show-last-auto-complete-result))) 1665 | 1666 | (provide 'omnisharp) 1667 | 1668 | ;;; omnisharp.el ends here 1669 | 1670 | --------------------------------------------------------------------------------