support to come soon (via server fix))."
57 | (interactive)
58 | (insert char)
59 |
60 | (-let ((request (-concat (omnisharp--get-request-object)
61 | `((Character . ,char))))
62 | (buffer (current-buffer)))
63 |
64 | (omnisharp--send-command-to-server-sync
65 | "formatAfterKeystroke"
66 | request
67 | (-lambda ((&alist 'Changes text-changes))
68 | (--map (omnisharp--apply-text-change-to-buffer it buffer)
69 | text-changes)))))
70 |
71 |
72 | (defun omnisharp-fix-usings ()
73 | "Find usages for the symbol under point."
74 | (interactive)
75 | (let*((fixusings-request
76 | (->> (omnisharp--get-request-object)
77 | (cons `(WantsTextChanges . true))))
78 | (buffer (current-buffer)))
79 |
80 | (omnisharp--send-command-to-server-sync
81 | "fixusings"
82 | fixusings-request
83 | (lambda (fixusings-response) (omnisharp--fixusings-worker
84 | fixusings-response
85 | buffer)))))
86 |
87 | (defun omnisharp--fixusings-worker (fixusings-response
88 | buffer)
89 | (-if-let (error-message (cdr (assoc 'ErrorMessage fixusings-response)))
90 | (omnisharp--message error-message)
91 | (-let (((&alist 'AmbiguousResults quickfixes) fixusings-response))
92 | (if (> (length quickfixes) 0)
93 | (omnisharp--write-quickfixes-to-compilation-buffer
94 | quickfixes
95 | omnisharp--ambiguous-symbols-buffer-name
96 | omnisharp-ambiguous-results-header)))
97 | (-let (((&alist 'Changes text-changes) fixusings-response))
98 | (--map (omnisharp--apply-text-change-to-buffer it buffer)
99 | text-changes))))
100 |
101 | (provide 'omnisharp-format-actions)
102 |
--------------------------------------------------------------------------------
/omnisharp-code-structure.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 | (require 'dash)
17 |
18 | (defun omnisharp--cs-inspect-buffer (callback)
19 | "Calls into the /v2/codestructure endpoint to retrieve code structure for
20 | the current buffer from the server and invokes CALLBACK with single argument
21 | that contains sequence of elements retrieved.
22 |
23 | Element sequence is hierarchical -- see the 'Children property for each element
24 | to inspect it resursively or invoke 'omnisharp--cs-filter-resursively on elements
25 | to grab the things you need out of the tree."
26 |
27 | (omnisharp--send-command-to-server
28 | "/v2/codestructure"
29 | (omnisharp--get-request-object)
30 | (-lambda ((&alist 'Elements elements))
31 | (funcall callback elements))))
32 |
33 |
34 | (defun omnisharp--cs-inspect-elements-recursively (fn elements)
35 | "Invokes FN on each of elements on the ELEMENTS tree
36 | in a depth-first fashion."
37 | (seq-each
38 | (lambda (el)
39 | (funcall fn el)
40 | (-let* (((&alist 'Children children) el))
41 | (omnisharp--cs-inspect-elements-recursively fn children)))
42 | elements))
43 |
44 |
45 | (defun omnisharp--cs-filter-resursively (predicate elements)
46 | "Filters out code elements in the sequence given and returns
47 | a list of elements that match the predicate given."
48 |
49 | (let ((results nil))
50 | (omnisharp--cs-inspect-elements-recursively
51 | (lambda (el)
52 | (if (funcall predicate el)
53 | (setq results (cons el results))))
54 | elements)
55 | results))
56 |
57 |
58 | (defun omnisharp--cs-l-c-within-range (l c range)
59 | "Returns 't when L (line) and C (column) are within the RANGE."
60 |
61 | (-let* (((&alist 'Start start 'End end) range)
62 | ((&alist 'Line start-l 'Column start-c) start)
63 | ((&alist 'Line end-l 'Column end-c) end))
64 | (or (and (= l start-l) (>= c start-c) (or (> end-l start-l) (<= c end-c)))
65 | (and (> l start-l) (< l end-l))
66 | (and (= l end-l) (<= c end-c)))))
67 |
68 |
69 | (defun omnisharp--cs-element-stack-on-l-c (l c elements)
70 | "Returns a list of elements that enclose a point in file specified
71 | by L (line) and C (column). If the point is enclosed by any of the ELEMENTS
72 | the result contains hierarchical list of namespace, class and [method|field]
73 | elements."
74 |
75 | (let ((matching-element (seq-find (lambda (el)
76 | (-let* (((&alist 'Ranges ranges) el)
77 | ((&alist 'full full-range) ranges))
78 | (omnisharp--cs-l-c-within-range l c full-range)))
79 | elements)))
80 | (if matching-element
81 | (-let (((&alist 'Children children) matching-element))
82 | (cons matching-element (omnisharp--cs-element-stack-on-l-c l c children))))))
83 |
84 |
85 | (defun omnisharp--cs-element-stack-at-point (callback)
86 | "Invokes callback with a stack of code elements on point in the current buffer"
87 | (omnisharp--cs-inspect-buffer
88 | (lambda (elements)
89 | (let ((pos-line (line-number-at-pos))
90 | (pos-col (current-column)))
91 | (funcall callback
92 | (omnisharp--cs-element-stack-on-l-c pos-line pos-col elements))))))
93 |
94 |
95 | (defun omnisharp--cs-unit-test-method-p (el)
96 | "Returns a list (test-method-name test-framework) if the element
97 | given is a test method, nil otherwise."
98 |
99 | (-let* (((&alist 'Kind kind
100 | 'Properties properties) el)
101 | ((&alist 'testMethodName test-method-name
102 | 'testFramework test-framework) properties))
103 | (if (and test-method-name test-framework)
104 | (list test-method-name test-framework))))
105 |
106 |
107 | (provide 'omnisharp-code-structure)
108 |
109 |
--------------------------------------------------------------------------------
/test/server-management-test.el:
--------------------------------------------------------------------------------
1 |
2 | ;; This file is free software; you can redistribute it and/or modify
3 | ;; it under the terms of the GNU General Public License as published by
4 | ;; the Free Software Foundation; either version 3, or (at your option)
5 | ;; any later version.
6 |
7 | ;; This file is distributed in the hope that it will be useful,
8 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | ;; GNU General Public License for more details.
11 |
12 | ;; You should have received a copy of the GNU General Public License
13 | ;; along with this program. If not, see .
14 |
15 |
16 | (defmacro with-test-omnisharp-roslyn-process (process-symbol &rest test-forms)
17 | `(unwind-protect
18 | (progn
19 | (let ((p (start-process "mock-omnisharp-roslyn-test-process"
20 | "mock-omnisharp-roslyn-test-process" ; buffer name
21 | "cat")))
22 | (with-current-buffer "mock-omnisharp-roslyn-test-process"
23 | (erase-buffer))
24 | ;; allow using the process outside this macro
25 | (setq ,process-symbol p)
26 | ,@test-forms))
27 | (kill-process "mock-omnisharp-roslyn-test-process")))
28 |
29 | (ert-deftest omnisharp--read-lines-from-process-output-test ()
30 | ;; A single message in its entirety. Should return the message.
31 | (with-test-omnisharp-roslyn-process
32 | process
33 | (should (equal '("{Some: Json}")
34 | (omnisharp--read-lines-from-process-output
35 | process
36 | ;; fake json does not confuse emacs syntax
37 | ;; highlighting when editing
38 | "{Some: Json}\n"))))
39 |
40 | ;; Partial message. Should return nothing. The server will send
41 | ;; another message, which will include the rest of this message, and
42 | ;; the next call will return this line and any new lines added in
43 | ;; that call.
44 | (with-test-omnisharp-roslyn-process
45 | process
46 | (should (equal '()
47 | (omnisharp--read-lines-from-process-output
48 | process
49 | ;; notice no newline at the end! This means the
50 | ;; message is divided into more parts
51 | "{This message would continue in the next part..."))))
52 |
53 | ;; A response message arriving in two parts. Should return the full
54 | ;; line and the next call should return the partial line and any new
55 | ;; lines added in that call. So the messages arriving are:
56 | ;; 1. Message start
57 | ;; 2. Message end
58 | (with-test-omnisharp-roslyn-process
59 | process
60 | (progn
61 | (omnisharp--read-lines-from-process-output
62 | process
63 | "{Message start")
64 | (should (equal
65 | '("{Message start, and message end}")
66 | (omnisharp--read-lines-from-process-output
67 | process
68 | ", and message end}\n")))))
69 |
70 | ;; Two lines arriving like this:
71 | ;; 1. Message 1 part a
72 | ;; 2. Message 1 part b & message 2 and 3
73 | (with-test-omnisharp-roslyn-process
74 | process
75 | (progn
76 | (omnisharp--read-lines-from-process-output process "{Message start")
77 | (should (equal
78 | '("{Message start, message end}"
79 | "{Second message}"
80 | "{Third message}")
81 | (omnisharp--read-lines-from-process-output
82 | process
83 | ", message end}\n{Second message}\n{Third message}\n")))))
84 |
85 | ;; 1. Full messages with a partial message
86 | ;; 2. The rest of the partial message.
87 | ;; All messages will be returned after the second call.
88 | (with-test-omnisharp-roslyn-process
89 | process
90 | (progn
91 | (omnisharp--read-lines-from-process-output
92 | process "{First message}\n{Second message}\n{Third message start")
93 | (should (equal
94 | '("{First message}"
95 | "{Second message}"
96 | "{Third message start, third message end}")
97 | (omnisharp--read-lines-from-process-output
98 | process
99 | ", third message end}\n")))
100 |
101 | ;; a new message should not repeat any previous ones
102 | (should (equal
103 | '("{Fourth message}")
104 | (omnisharp--read-lines-from-process-output
105 | process
106 | "{Fourth message}\n"))))))
107 |
108 | (ert-deftest omnisharp--handle-server-response-packet ()
109 | ;; should call response-handler when a response with a matching
110 | ;; request id is received
111 | (let* ((response-body "lalala")
112 | (response `((Success . t)
113 | (Message . "message")
114 | (Body . ,response-body)
115 | (Command . "getfoodata")
116 | (Request_seq . 1)))
117 | (server-info
118 | `((:process . nil)
119 | (:request-id . 1)
120 | (:response-handlers . ((1 . (lambda (body)
121 | (should (equal body ,response-body)))))))))
122 |
123 | (omnisharp--handle-server-response-packet response server-info)
124 |
125 | ;; should have removed handlers for request-id 1 in server-info
126 | (should (--none? (= (car it) 1)
127 | (cdr (assoc :response-handlers server-info)))))
128 |
129 | ;; should not call handler when a response for another request is
130 | ;; received (when there is no matching handler)
131 | (let* ((response `((Success . t)
132 | (Message . "message")
133 | (Body . "not used")
134 | (Command . "getfoodata")
135 | (Request_seq . 1)))
136 | (server-info
137 | `((:process . nil)
138 | (:request-id . 1)
139 | (:response-handlers
140 | . ((2 . (lambda (body)
141 | ;; just fail
142 | (should
143 | (equal nil
144 | "error: should not have been called")))))))))
145 |
146 | (should (equal nil
147 | (omnisharp--handle-server-response-packet response server-info)))))
148 |
--------------------------------------------------------------------------------
/omnisharp-server-installation.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 | (require 'gnutls)
17 | (require 'dash)
18 |
19 | (defun omnisharp--server-installation-dir ()
20 | "Returns installation directory for automatic server installation."
21 | (f-join omnisharp-cache-directory "server" (concat "v" omnisharp-expected-server-version)))
22 |
23 | (defun omnisharp--server-installation-executable-name ()
24 | (if (eq system-type 'windows-nt)
25 | "OmniSharp.exe"
26 | "run"))
27 |
28 | (defun omnisharp--server-installation-path (&rest ok-if-missing)
29 | "Returns path to installed omnisharp server binary, if any."
30 | (let* ((executable-name (omnisharp--server-installation-executable-name))
31 | (executable-path (f-join (omnisharp--server-installation-dir) executable-name)))
32 | (if (or (f-exists-p executable-path) ok-if-missing)
33 | executable-path
34 | nil)))
35 |
36 | (defun omnisharp--server-installation-download-and-extract (url filename reinstall)
37 | "Downloads and extracts a tgz/zip into it's parent directory."
38 |
39 | ;; remove the file if reinstall is set
40 | (if (and reinstall (f-exists-p filename))
41 | (f-delete filename))
42 |
43 | (unless (f-exists-p filename)
44 | (message (format "omnisharp: downloading server binary from \"%s\"..." url))
45 | (let ((gnutls-algorithm-priority
46 | (if (and (not gnutls-algorithm-priority)
47 | (boundp 'libgnutls-version)
48 | (>= libgnutls-version 30603)
49 | (version<= emacs-version "26.2"))
50 | "NORMAL:-VERS-TLS1.3"
51 | gnutls-algorithm-priority)))
52 | (url-copy-file url filename t)))
53 |
54 | (let ((target-dir (f-dirname filename)))
55 | (message (format "omnisharp: extracting \"%s\" into \"%s\""
56 | (f-filename filename)
57 | target-dir))
58 |
59 | (cond
60 | ((eq system-type 'windows-nt)
61 | ;; on windows, we attempt to use powershell v5+, available on Windows 10+
62 | (let ((powershell-version (substring
63 | (shell-command-to-string "powershell -command \"(Get-Host).Version.Major\"")
64 | 0 -1)))
65 | (if (>= (string-to-number powershell-version) 5)
66 | (call-process "powershell"
67 | nil
68 | nil
69 | nil
70 | "-command"
71 | (concat "add-type -assembly system.io.compression.filesystem;"
72 | "[io.compression.zipfile]::ExtractToDirectory(\"" filename "\", \"" target-dir "\")"))
73 |
74 | (message (concat "omnisharp: for the 'M-x omnisharp-install-server' "
75 | " command to work on Windows you need to have powershell v5+ installed")))))
76 |
77 | ((or (eq system-type 'gnu/linux)
78 | (eq system-type 'darwin))
79 | (call-process "tar" nil nil t "xf" filename "-C" target-dir))
80 |
81 | (t (signal "omnisharp-install-server does not support platform %s (yet)" system-type)))))
82 |
83 | (defun omnisharp--server-installation-tarball-name ()
84 | "Resolves a tarball or zip file to use for this installation.
85 | Note that due to a bug in emacs on Windows we currently use the x86/32bit version.
86 | See https://github.com/OmniSharp/omnisharp-emacs/issues/315"
87 | (cond ((eq system-type 'windows-nt) "omnisharp-win-x86.zip")
88 | ((eq system-type 'darwin) "omnisharp-osx.tar.gz")
89 | ((and (eq system-type 'gnu/linux)
90 | (or (eq (string-match "^x86_64" system-configuration) 0)
91 | (eq (string-match "^i[3-6]86" system-configuration) 0))) "omnisharp-linux-x64.tar.gz")
92 | (t "omnisharp-mono.tar.gz")))
93 |
94 | (defun omnisharp--install-server (reinstall &rest silent-installation)
95 | "Implementation for autoloaded omnisharp-install-server in omnisharp.el.
96 |
97 | REINSTALL can be set 't to force reinstallation.
98 | SILENT-INSTALLATION value of 't means user is not involved."
99 | (let* ((server-dir (omnisharp--server-installation-dir))
100 | (distro-tarball (omnisharp--server-installation-tarball-name))
101 | (distro-url (concat "https://github.com/OmniSharp/omnisharp-roslyn/releases/download"
102 | "/v" omnisharp-expected-server-version
103 | "/" distro-tarball))
104 | (expected-executable-path (omnisharp--server-installation-path t)))
105 | (if (or reinstall (not (f-exists-p expected-executable-path)))
106 | (if (or silent-installation
107 | (y-or-n-p (format "omnisharp: this will download and extract ~20-30 MB from \"%s\"; do you want to continue?"
108 | distro-url)))
109 | (progn
110 | (message (format "omnisharp: attempting to download and install OmniSharp server into %s"
111 | server-dir))
112 | (omnisharp--mkdirp server-dir)
113 | (omnisharp--server-installation-download-and-extract
114 | distro-url
115 | (f-join server-dir distro-tarball)
116 | reinstall)
117 | (let ((executable-path (omnisharp--server-installation-path)))
118 | (if executable-path
119 | (if (not silent-installation)
120 | (message (format "omnisharp: server was installed as \"%s\"; you can now do M-x 'omnisharp-start-omnisharp-server' "
121 | executable-path)))
122 | (message (concat "omnisharp: server could not be installed automatically. "
123 | "Please check https://github.com/OmniSharp/omnisharp-emacs/blob/master/doc/server-installation.md for instructions."))))))
124 | (if (not silent-installation)
125 | (message (format "omnisharp: server is already installed (%s)"
126 | expected-executable-path))))))
127 |
128 | (provide 'omnisharp-server-installation)
129 |
--------------------------------------------------------------------------------
/omnisharp-solution-actions.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 | (require 'dash)
17 |
18 | (defun omnisharp--prepare-solution-errors-buffer ()
19 | "Makes a new *omnisharp-solution-errors* buffer or creates a new one
20 | and enabled compilation-mode on the buffer."
21 | (let ((existing-buffer (get-buffer "*omnisharp-solution-errors*"))
22 | (solution-root-dir (cdr (assoc :project-root omnisharp--server-info))))
23 | (if existing-buffer
24 | (progn
25 | (with-current-buffer existing-buffer
26 | (setq buffer-read-only nil)
27 | (erase-buffer)
28 | (setq buffer-read-only t)
29 | (setq default-directory solution-root-dir))
30 | existing-buffer)
31 | (let ((buffer (get-buffer-create "*omnisharp-solution-errors*")))
32 | (with-current-buffer buffer
33 | (setq default-directory solution-root-dir)
34 | (compilation-mode)
35 | buffer)))))
36 |
37 | (defun omnisharp-solution-errors (&optional errors-only)
38 | "Opens a new buffer *omnisharp-solution-errors* (or updates existing one)
39 | with solution errors. This is the same error list as emitted by flycheck only
40 | for the whole solution."
41 | (interactive "P")
42 | (if omnisharp--server-info
43 | ;; we want to show solutions error buffer early
44 | (let ((buffer (omnisharp--prepare-solution-errors-buffer))
45 | (time-started (current-time)))
46 | (with-current-buffer buffer
47 | (setq buffer-read-only nil)
48 | (insert "omnisharp-solution-errors: waiting for omnisharp server ...")
49 | (setq buffer-read-only t))
50 | (display-buffer buffer)
51 |
52 | ;; actually invoke the request
53 | (omnisharp--send-command-to-server
54 | "codecheck"
55 | `((FileName . nil))
56 | (lambda (response)
57 | (let ((buffer (omnisharp--prepare-solution-errors-buffer))
58 | (error-list (omnisharp--vector-to-list (cdr (assoc 'QuickFixes response))))
59 | (time-elapsed-seconds (time-to-seconds (time-subtract (current-time) time-started))))
60 | (display-buffer buffer)
61 | (save-window-excursion
62 | (with-current-buffer buffer
63 | (setq buffer-read-only nil)
64 | (dolist (item error-list)
65 | (let ((log-level (if (string= (cdr (assoc 'LogLevel item)) "Error") "error" "warning"))
66 | (filename (string-remove-prefix (concat default-directory "/") (cdr (assoc 'FileName item))))
67 | (line (cdr (assoc 'Line item)))
68 | (col (cdr (assoc 'Column item)))
69 | (text (cdr (assoc 'Text item)))
70 | (id (or (cdr (assoc 'Id item)) "CS0000")))
71 | (if (or (not errors-only)
72 | (string= log-level "error"))
73 | (insert (concat filename
74 | "(" (number-to-string line) "," (number-to-string col) "): "
75 | log-level " " id ": "
76 | text
77 | "\n")))))
78 | (insert (concat "\nomnisharp-solution-errors: finished, "
79 | "took " (number-to-string time-elapsed-seconds) " seconds to complete.\n"))
80 | (setq buffer-read-only t)))))))))
81 |
82 | (defun omnisharp-run-code-action-refactoring ()
83 | "Gets a list of refactoring code actions for the current editor
84 | position and file from the server. Asks the user what kind of
85 | refactoring they want to run. Then runs the action."
86 | (interactive)
87 | (let ((code-actions-request (omnisharp--get-code-actions-request)))
88 | (omnisharp--send-command-to-server
89 | "v2/getcodeactions"
90 | code-actions-request
91 | (-lambda ((&alist 'CodeActions code-actions))
92 | (let* ((code-actions (omnisharp--vector-to-list code-actions))
93 | (action-names (--map (cdr (assoc 'Name it))
94 | code-actions)))
95 | (if (<= (length action-names) 0)
96 | (message "No refactorings available at this position.")
97 |
98 | (with-local-quit
99 | (let* ((chosen-action-name (omnisharp--completing-read
100 | "Run code action: "
101 | action-names))
102 | (chosen-action
103 | (--first (equal (cdr (assoc 'Name it))
104 | chosen-action-name)
105 | code-actions)))
106 |
107 | (omnisharp-run-code-action-refactoring-worker
108 | (cdr (assoc 'Identifier chosen-action))
109 | code-actions-request)))))))))
110 |
111 | (defun omnisharp-run-code-action-refactoring-worker (chosen-action-identifier
112 | code-actions-request)
113 | (let* ((run-code-action-request
114 | (-concat code-actions-request
115 | `((Identifier . ,chosen-action-identifier)
116 | (WantsTextChanges . t)))))
117 | (omnisharp--send-command-to-server-sync
118 | "v2/runcodeaction"
119 | run-code-action-request
120 | (-lambda ((&alist 'Changes modified-file-responses))
121 | (-map #'omnisharp--apply-text-changes
122 | modified-file-responses)))))
123 |
124 | (defun omnisharp--get-code-actions-request ()
125 | "Returns an ICodeActionRequest for the current buffer position"
126 | (if (region-active-p)
127 | (-concat (omnisharp--get-request-object)
128 | `((Selection . ((Start . ((Line . ,(omnisharp--region-start-line))
129 | (Column . ,(omnisharp--region-start-column))))
130 | (End . ((Line . ,(omnisharp--region-end-line))
131 | (Column . ,(omnisharp--region-end-column))))))))
132 | (omnisharp--get-request-object)))
133 |
134 | (defun omnisharp--convert-backslashes-to-forward-slashes
135 | (string-to-convert)
136 | "Converts the given STRING-TO-CONVERT's backslashes to forward
137 | slashes."
138 | (replace-regexp-in-string "\\\\" "/" string-to-convert))
139 |
140 | (provide 'omnisharp-solution-actions)
141 |
--------------------------------------------------------------------------------
/omnisharp-unit-test-actions.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 | (require 'dash)
17 |
18 | (defun omnisharp-unit-test-at-point ()
19 | "Runs test case under point, if any."
20 | (interactive)
21 | (omnisharp--cs-element-stack-at-point
22 | (lambda (stack)
23 | (let* ((element-on-point (car (last stack)))
24 | (test-method (omnisharp--cs-unit-test-method-p element-on-point))
25 | (test-method-name (car test-method))
26 | (test-method-framework (car (cdr test-method))))
27 | (omnisharp--unit-test-start test-method-framework (list test-method-name))))))
28 |
29 | (defun omnisharp-unit-test-buffer ()
30 | "Runs all test cases defined in the current buffer."
31 | (interactive)
32 | (omnisharp--cs-inspect-buffer
33 | (lambda (elements)
34 | (let* ((test-methods (omnisharp--cs-filter-resursively
35 | 'omnisharp--cs-unit-test-method-p
36 | elements))
37 | (test-method-framework (car (cdr (omnisharp--cs-unit-test-method-p (car test-methods)))))
38 | (test-method-names (mapcar (lambda (method)
39 | (car (omnisharp--cs-unit-test-method-p method)))
40 | test-methods)))
41 | (omnisharp--unit-test-start test-method-framework test-method-names)))))
42 |
43 | (defun omnisharp-unit-test-last ()
44 | "Re-runs the last unit test run (if any)."
45 | (interactive)
46 | (let ((last-unit-test (cdr (assoc :last-unit-test omnisharp--server-info))))
47 | (apply 'omnisharp--unit-test-start (or last-unit-test (list nil nil)))))
48 |
49 | (defun omnisharp--unit-test-start (test-method-framework test-method-names)
50 | "Runs tests specified by test method name"
51 | (if (and test-method-framework test-method-names)
52 | (let ((request-message (-concat
53 | (omnisharp--get-request-object)
54 | `((TestFrameworkName . ,test-method-framework)
55 | (MethodNames . ,test-method-names)))))
56 | (setcdr (assoc :last-unit-test omnisharp--server-info)
57 | (list test-method-framework test-method-names))
58 | (omnisharp--unit-test-reset-test-results-buffer t)
59 | (omnisharp--send-command-to-server
60 | "/v2/runtestsinclass"
61 | request-message
62 | (-lambda ((&alist 'Results results 'Pass passed))
63 | (omnisharp--unit-test-emit-results passed results))))
64 | (omnisharp--message "omnisharp: No Test Methods to run")))
65 |
66 | (defun omnisharp--unit-test-emit-results (passed results)
67 | "Emits unit test results as returned by the server to the unit test result buffer.
68 | PASSED is t if all of the results have passed. RESULTS is a vector of status data for
69 | each of the unit tests ran."
70 | ; we want to clean output buffer for result if things have passed otherwise
71 | ; compilation & test run output is to be cleared and results shown only for brevity
72 |
73 | (omnisharp--unit-test-message "")
74 |
75 | (seq-doseq (result results)
76 | (-let* (((&alist 'MethodName method-name
77 | 'Outcome outcome
78 | 'ErrorMessage error-message
79 | 'ErrorStackTrace error-stack-trace
80 | 'StandardOutput stdout
81 | 'StanderError stderr) result)
82 | (outcome-is-passed (string-equal "passed" outcome)))
83 |
84 | (omnisharp--unit-test-message
85 | (format "[%s] %s "
86 | (propertize
87 | (upcase outcome)
88 | 'font-lock-face (if outcome-is-passed
89 | '(:foreground "green" :weight bold)
90 | '(:foreground "red" :weight bold)))
91 | (omnisharp--truncate-symbol-name method-name 76)))
92 |
93 | (unless outcome-is-passed
94 | (omnisharp--unit-test-message error-message)
95 |
96 | (if error-stack-trace
97 | (omnisharp--unit-test-message error-stack-trace))
98 |
99 | (unless (= (seq-length stdout) 0)
100 | (omnisharp--unit-test-message "Standard output:")
101 | (seq-doseq (stdout-line stdout)
102 | (omnisharp--unit-test-message stdout-line)))
103 |
104 | (unless (= (seq-length stderr) 0)
105 | (omnisharp--unit-test-message "Standard error:")
106 | (seq-doseq (stderr-line stderr)
107 | (omnisharp--unit-test-message stderr-line)))
108 | )))
109 |
110 | (omnisharp--unit-test-message "")
111 |
112 | (if (eq passed :json-false)
113 | (omnisharp--unit-test-message
114 | (propertize "*** UNIT TEST RUN HAS FAILED ***"
115 | 'font-lock-face '(:foreground "red" :weight bold)))
116 | (omnisharp--unit-test-message
117 | (propertize "*** UNIT TEST RUN HAS SUCCEEDED ***"
118 | 'font-lock-face '(:foreground "green" :weight bold)))
119 | )
120 | nil)
121 |
122 | (defun omnisharp--unit-test-message (message)
123 | (let ((existing-buffer (get-buffer omnisharp--unit-test-results-buffer-name)))
124 | (if existing-buffer
125 | (with-current-buffer existing-buffer
126 | (setq buffer-read-only nil)
127 | (goto-char (point-max))
128 | (insert message)
129 | (insert "\n")
130 | (setq buffer-read-only t)))))
131 |
132 | (defun omnisharp--handle-test-message-event (message)
133 | "This is hooked into omnisharp 'TestMessage event and when handling an
134 | event will emit any test action output to unit test output buffer."
135 | (-let* (
136 | ((&alist 'Body body) message)
137 | ((&alist 'Message log-message) body))
138 | (omnisharp--unit-test-message log-message)))
139 |
140 | (defun omnisharp--unit-test-reset-test-results-buffer (present-buffer)
141 | "Creates new or reuses existing unit test result output buffer."
142 | (let ((existing-buffer (get-buffer omnisharp--unit-test-results-buffer-name))
143 | (solution-root-dir (cdr (assoc :project-root omnisharp--server-info))))
144 | (if existing-buffer
145 | (progn
146 | (with-current-buffer existing-buffer
147 | (setq buffer-read-only nil)
148 | (erase-buffer)
149 | (setq buffer-read-only t)
150 | (setq default-directory solution-root-dir))
151 | existing-buffer)
152 | (let ((buffer (get-buffer-create omnisharp--unit-test-results-buffer-name)))
153 | (with-current-buffer buffer
154 | (setq default-directory solution-root-dir)
155 | (compilation-mode)
156 | buffer))))
157 |
158 | (if present-buffer
159 | (display-buffer omnisharp--unit-test-results-buffer-name)))
160 |
161 | (provide 'omnisharp-unit-test-actions)
162 |
--------------------------------------------------------------------------------
/omnisharp-current-symbol-actions.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 | (require 'dash)
17 |
18 | (defun omnisharp-current-type-information (&optional add-to-kill-ring)
19 | "Display information of the current type under point. With prefix
20 | argument, add the displayed result to the kill ring. This can be used
21 | to insert the result in code, for example."
22 | (interactive "P")
23 | (omnisharp-current-type-information-worker 'Type add-to-kill-ring))
24 |
25 | (defun omnisharp-current-type-documentation (&optional add-to-kill-ring)
26 | "Display documentation of the current type under point. With prefix
27 | argument, add the displayed result to the kill ring. This can be used
28 | to insert the result in code, for example."
29 | (interactive "P")
30 | (omnisharp-current-type-information-worker 'Documentation add-to-kill-ring))
31 |
32 | (defun omnisharp-current-type-information-worker (type-property-name
33 | &optional add-to-kill-ring)
34 | "Get type info from the API and display a part of the response as a
35 | message. TYPE-PROPERTY-NAME is a symbol in the type lookup response
36 | from the server side, i.e. 'Type or 'Documentation that will be
37 | displayed to the user."
38 | (omnisharp--send-command-to-server
39 | "typelookup"
40 | (omnisharp--get-typelookup-request-object)
41 | (lambda (response)
42 | (let ((stuff-to-display (cdr (assoc type-property-name
43 | response))))
44 | (omnisharp--message-at-point stuff-to-display)
45 | (when add-to-kill-ring
46 | (kill-new stuff-to-display))))))
47 |
48 | (defun omnisharp-current-type-information-to-kill-ring ()
49 | "Shows the information of the current type and adds it to the kill
50 | ring."
51 | (interactive)
52 | (omnisharp-current-type-information t))
53 |
54 | (defun omnisharp-find-usages ()
55 | "Find usages for the symbol under point"
56 | (interactive)
57 | (omnisharp--message "Finding usages...")
58 | (omnisharp--send-command-to-server
59 | "findusages"
60 | (omnisharp--get-request-object)
61 | (-lambda ((&alist 'QuickFixes quickfixes))
62 | (omnisharp--find-usages-show-response quickfixes))))
63 |
64 | (defun omnisharp--find-usages-show-response (quickfixes)
65 | (if (equal 0 (length quickfixes))
66 | (omnisharp--message-at-point "No usages found.")
67 | (omnisharp--write-quickfixes-to-compilation-buffer
68 | quickfixes
69 | omnisharp--find-usages-buffer-name
70 | omnisharp-find-usages-header)))
71 |
72 | (defun omnisharp-find-implementations-with-ido (&optional other-window)
73 | (interactive "P")
74 | (omnisharp--send-command-to-server-sync
75 | "findimplementations"
76 | (omnisharp--get-request-object)
77 | (lambda (quickfix-response)
78 | (omnisharp--show-or-navigate-to-quickfixes-with-ido quickfix-response
79 | other-window))))
80 |
81 | (defun omnisharp--show-or-navigate-to-quickfixes-with-ido (quickfix-response
82 | &optional other-window)
83 | (-let (((&alist 'QuickFixes quickfixes) quickfix-response))
84 | (cond ((equal 0 (length quickfixes))
85 | (omnisharp--message "No implementations found."))
86 | ((equal 1 (length quickfixes))
87 | (omnisharp-go-to-file-line-and-column (-first-item (omnisharp--vector-to-list quickfixes))
88 | other-window))
89 | (t
90 | (omnisharp--choose-and-go-to-quickfix-ido quickfixes other-window)))))
91 |
92 | (defun omnisharp-find-usages-with-ido (&optional other-window)
93 | (interactive "P")
94 | (omnisharp--send-command-to-server
95 | "findusages"
96 | (omnisharp--get-request-object)
97 | (lambda (quickfix-response)
98 | (omnisharp--show-or-navigate-to-quickfixes-with-ido quickfix-response
99 | other-window))))
100 |
101 | (defun omnisharp-find-implementations ()
102 | "Show a buffer containing all implementations of the interface under
103 | point, or classes derived from the class under point. Allow the user
104 | to select one (or more) to jump to."
105 | (interactive)
106 | (omnisharp--message "Finding implementations...")
107 | (omnisharp-find-implementations-worker
108 | (omnisharp--get-request-object)
109 | (lambda (quickfixes)
110 | (cond ((equal 0 (length quickfixes))
111 | (omnisharp--message "No implementations found."))
112 |
113 | ;; Go directly to the implementation if there only is one
114 | ((equal 1 (length quickfixes))
115 | (omnisharp-go-to-file-line-and-column (car quickfixes)))
116 |
117 | (t
118 | (omnisharp--write-quickfixes-to-compilation-buffer
119 | quickfixes
120 | omnisharp--find-implementations-buffer-name
121 | omnisharp-find-implementations-header))))))
122 |
123 | (defun omnisharp-find-implementations-worker (request callback)
124 | "Gets a list of QuickFix lisp objects from a findimplementations api call
125 | asynchronously. On completions, CALLBACK is run with the quickfixes as its only argument."
126 | (omnisharp--send-command-to-server
127 | "findimplementations"
128 | request
129 | (-lambda ((&alist 'QuickFixes quickfixes))
130 | (apply callback (list (omnisharp--vector-to-list quickfixes))))))
131 |
132 | (defun omnisharp-rename ()
133 | "Rename the current symbol to a new name. Lets the user choose what
134 | name to rename to, defaulting to the current name of the symbol."
135 | (interactive)
136 | (let* ((current-word (thing-at-point 'symbol))
137 | (rename-to (read-string "Rename to: " current-word))
138 | (rename-request
139 | (->> (omnisharp--get-request-object)
140 | (cons `(RenameTo . ,rename-to))
141 | (cons `(WantsTextChanges . true))))
142 | (location-before-rename
143 | (omnisharp--get-request-object-for-emacs-side-use)))
144 | (omnisharp--send-command-to-server-sync
145 | "rename"
146 | rename-request
147 | (lambda (rename-response) (omnisharp--rename-worker
148 | rename-response
149 | location-before-rename)))))
150 |
151 | (defun omnisharp--rename-worker (rename-response
152 | location-before-rename)
153 | (-if-let (error-message (cdr (assoc 'ErrorMessage rename-response)))
154 | (omnisharp--message error-message)
155 | (-let (((&alist 'Changes modified-file-responses) rename-response))
156 | ;; The server will possibly update some files that are currently open.
157 | ;; Save all buffers to avoid conflicts / losing changes
158 | (save-some-buffers t)
159 |
160 | (-map #'omnisharp--apply-text-changes modified-file-responses)
161 |
162 | ;; Keep point in the buffer that initialized the rename so that
163 | ;; the user does not feel disoriented
164 | (omnisharp-go-to-file-line-and-column location-before-rename)
165 |
166 | (omnisharp--message "Rename complete in files: \n%s"
167 | (-interpose "\n" (--map (omnisharp--get-filename it)
168 | modified-file-responses))))))
169 |
170 | (defun omnisharp--apply-text-changes (modified-file-response)
171 | (-let (((&alist 'Changes changes) modified-file-response))
172 | (omnisharp--update-files-with-text-changes
173 | (omnisharp--get-filename modified-file-response)
174 | (omnisharp--vector-to-list changes))))
175 |
176 | (provide 'omnisharp-current-symbol-actions)
177 |
--------------------------------------------------------------------------------
/doc/features.md:
--------------------------------------------------------------------------------
1 | ## Features
2 |
3 | * Contextual code completion (i.e. auto-complete / IntelliSense) using
4 | [popup.el][] or [ido-mode][] or [company-mode][] if it is installed.
5 | Currently popup and ido-mode can complete symbols in all namespaces
6 | if so configured.
7 | * Popup.el and company-mode provide a more sophisticated
8 | interface, with the possibility to fall back on all of ido's
9 | flexible matching power.
10 | * Also shows documentation like other IDEs
11 | * Show type of the current symbol in the minibuffer. With prefix
12 | argument, add it to kill ring. Optional eldoc support available to
13 | show this automatically when point is on a symbol (see the source
14 | for help)
15 | * Navigation helpers
16 | * Go to definition of a type/variable/method etc. With the prefix
17 | argument (C-u), use another window.
18 | * Find usages of the current symbol in the solution
19 | * Find implementations/derived types of the current type
20 | * Go to definition of a type in the current file with [ido-mode][]
21 | (fast).
22 | * Go to definition of a member in the current type with
23 | [ido-mode][] (likewise fast :)).
24 | * Go to region / endregion in current file
25 | * Go to any member in the solution (property, method etc.)
26 | * Go to file, then go to member (type, property, method) in that
27 | file.
28 | * Rename the current symbol and all references to it
29 | * Rename only semantic references ("smart" rename)
30 | * Rename as verbatim text ("dumb" rename)
31 | * Solution manipulation
32 | * Add/remove the current file
33 | * Add/remove selected files in the dired directory editor
34 | * The user may choose whether they want to build in the emacs
35 | `*compilation*` buffer or at OmniSharp's end (non-asynchronous,
36 | that is, blocking)
37 | * Jump to errors like in normal `*compilation*` output
38 | * Request for a list of compilation errors/warnings directly from
39 | omnisharp server w/o a compilation, using
40 | `M-x omnisharp-solution-errors`
41 | * Override selected superclass member
42 | * Run a refactoring on the current position
43 | * Uses the refactorings from the NRefactory library, which is also
44 | used by the MonoDevelop and SharpDevelop IDEs
45 | * When used with a selection, prompts to extract a method from the
46 | selection where possible
47 | * Format the current buffer
48 | * Currently only one formatting style supported, easy to add more.
49 | * Fix using statements
50 | * Sorts, removes and adds any missing using statements
51 | for the current buffer
52 | * Syntax checker for parse errors
53 | * Runs using the provided [Flycheck][] checker in the background.
54 | * Syntax checker for code issues (refactoring suggestions)
55 | * This automatically runs when there are no syntax errors
56 | * Fix the first suggested error on the current line with
57 | `omnisharp-fix-code-issue-at-point`
58 | * OmniSharp server instance manipulation
59 | * Start server
60 | * Test runner
61 | * Can run test at point, fixture or all tests in project.
62 |
63 | ## Details
64 |
65 | ### Autocompletion
66 |
67 | #### company-mode interface
68 |
69 | company-mode showing parameters and return values, and the selected
70 | function description in the minibuffer. As you can see, the completion
71 | works with non-trivial code.
72 |
73 | 
74 |
75 | Pressing F1 with a candidate selected in the the company-mode popup
76 | shows a buffer with documentation.
77 |
78 | 
79 |
80 | Omnisharp's company-mode support ignores case by default, but can be
81 | made case sensitive by setting `omnisharp-company-ignore-case` to nil.
82 |
83 | #### popup.el interface
84 |
85 | 
86 |
87 | popup.el with documentation. The documentation may be disabled if you
88 | need the screen space. There is an option to show documentation in a
89 | help buffer.
90 |
91 | To (not) complete from all namespaces, use the prefix argument when
92 | calling. This inverts the
93 | `omnisharp-auto-complete-want-importable-types` setting temporarily.
94 |
95 | 
96 |
97 | #### Ido interface
98 |
99 | Ido allows for flexible matching of all text that the completions
100 | have. Each pressed character will narrow the list down to fewer
101 | options. It's also possible to do a cross search at any point with a
102 | new search term by pressing C-SPC.
103 |
104 | This makes it really easy to e.g. narrow the list down to members that
105 | handle a specific type, such as bool.
106 |
107 | To (not) complete from all namespaces, use the prefix argument when
108 | calling. This inverts the
109 | `omnisharp-auto-complete-want-importable-types` setting temporarily.
110 |
111 | 
112 |
113 | ### Go to type in current file
114 | This is a standard functionality in e.g. Visual Studio.
115 | The types are shown in the order they occur in the source file.
116 |
117 | 
118 |
119 | ### Go to member in current type
120 | This too is standard in various IDEs. Using ido makes navigating fast
121 | and intuitive.
122 | The members too are shown in the order they occur in the source file.
123 |
124 | 
125 |
126 | ### Rename
127 | Renaming suggests the current type as a basis.
128 |
129 | 
130 |
131 | ### Overriding members
132 | When invoked, displays a list of possible override targets.
133 |
134 | 
135 |
136 | When a target is chosen, a stub member is inserted.
137 |
138 | 
139 |
140 | ### Refactoring suggestions
141 | For now, this must be manually invoked. It can do different things
142 | depending on the symbol under point. In this picture it has been
143 | invoked on a method parameter.
144 |
145 | 
146 |
147 | ### Solution building
148 | Here is an example of an asynchronous build within Emacs. It works by
149 | getting the build command from the backend and executing that in the
150 | compilation buffer.
151 |
152 | 
153 |
154 | ### Syntax errors checking
155 | It is possible to check the current buffer for syntax errors using the
156 | flycheck library. This is done asynchronously, and errors are shown
157 | when found. Note that this is not a type checker, only syntax is
158 | currently checked.
159 |
160 | 
161 |
162 | To start the check, use (flycheck-mode) or select it in the
163 | menu. The check will then be performed after the current buffer has
164 | been idle for a certain number of seconds or when it is saved,
165 | depending on your flycheck configuration.
166 |
167 | To make syntax checking start sooner/later, use:
168 | ```
169 | (setq flycheck-idle-change-delay 2) ; in seconds
170 | ```
171 |
172 | ### ElDoc integration
173 | ElDoc support is switched on by default. This shows type information
174 | for the symbol at point in the echo area.
175 | To switch it off, set `omnisharp-eldoc-support` to nil.
176 |
177 | 
178 |
179 | ### Imenu integration
180 | Omnisharp's Imenu support allows you to quickly view and jump to
181 | function and variable definitions within your file. This can be used
182 | either natively or in combination with helm-imenu
183 | Imenu support is off by default, but can be turned on by setting
184 | omnisharp-imenu-support to t
185 |
186 | ### Helm integration
187 |
188 | If you have Helm installed, Omnisharp offers several
189 | integrations. First of all, there's helm-imenu:
190 |
191 | 
192 |
193 | There's also 'omnisharp-helm-find-usages', which allows you to easily
194 | navigate to references in your project:
195 |
196 | 
197 |
198 | And then there's 'omnisharp-helm-find-symbols', which allows you find
199 | and jump to any symbol in your project:
200 |
201 | 
202 |
203 |
204 | ### company-mode integration
205 |
206 | To enable company-mode autocompletion, omnisharp requires at least
207 | version 0.7 of company-mode to be installed. Then add the following to
208 | your init file:
209 |
210 | ```
211 | (eval-after-load 'company
212 | '(add-to-list 'company-backends 'company-omnisharp))
213 | ```
214 |
215 | company-mode completion will only trigger when omnisharp-mode is active.
216 |
217 | ### Test runner integration
218 |
219 | Can run the test at point, fixture at point, or all tests
220 | in project.
221 |
222 | 
223 |
224 | Specify the path and parameters to your test runner on the server here :-
225 | https://github.com/nosami/OmniSharpServer/blob/0eb8644f67c020fc570aaf6629beabb7654ac944/OmniSharp/config.json#L10
226 |
--------------------------------------------------------------------------------
/omnisharp-settings.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t; -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 | ;; this file contains settings that are used throughout the project
17 |
18 | (require 'dash)
19 |
20 | (defgroup omnisharp ()
21 | "Omnisharp-emacs is a port of the awesome OmniSharp server to
22 | the Emacs text editor. It provides IDE-like features for editing
23 | files in C# solutions in Emacs, provided by an OmniSharp server
24 | instance that works in the background."
25 | :group 'external
26 | :group 'csharp)
27 |
28 | (defcustom omnisharp-host "http://localhost:2000/"
29 | "Currently expected to end with a / character."
30 | :group 'omnisharp
31 | :type 'string)
32 |
33 | (defvar omnisharp--find-usages-buffer-name "* OmniSharp : Usages *"
34 | "The name of the temporary buffer that is used to display the
35 | results of a 'find usages' call.")
36 |
37 | (defvar omnisharp--unit-test-results-buffer-name "* Omnisharp : Unit Test Results *"
38 | "The name of the temporary buffer that is used to display the results
39 | of a 'run tests' call.")
40 |
41 | (defvar omnisharp-debug nil
42 | "When non-nil, omnisharp-emacs will write entries a debug log")
43 |
44 | (defvar omnisharp--find-implementations-buffer-name "* OmniSharp : Implementations *"
45 | "The name of the temporary buffer that is used to display the
46 | results of a 'find implementations' call.")
47 |
48 | (defvar omnisharp--ambiguous-symbols-buffer-name "* OmniSharp : Ambiguous unresolved symbols *"
49 | "The name of the temporary buffer that is used to display any
50 | ambiguous unresolved symbols of a 'fix usings' call.")
51 |
52 | (defvar omnisharp-find-usages-header
53 | (concat "Usages in the current solution:"
54 | "\n\n")
55 | "This is shown at the top of the result buffer when
56 | omnisharp-find-usages is called.")
57 |
58 | (defvar omnisharp-find-implementations-header
59 | (concat "Implementations of the current interface / class:"
60 | "\n\n")
61 | "This is shown at the top of the result buffer when
62 | omnisharp-find-implementations is called.")
63 |
64 | (defvar omnisharp-ambiguous-results-header
65 | (concat "These results are ambiguous. You can run
66 | (omnisharp-run-code-action-refactoring) when point is on them to see
67 | options for fixing them."
68 | "\n\n")
69 | "This is shown at the top of the result buffer when
70 | there are ambiguous unresolved symbols after running omnisharp-fix-usings")
71 |
72 | (defcustom omnisharp-code-format-expand-tab t
73 | "Whether to expand tabs to spaces in code format requests."
74 | :group 'omnisharp
75 | :type '(choice (const :tag "Yes" t)
76 | (const :tag "No" nil)))
77 |
78 | (defvar omnisharp-mode-map
79 | (let ((map (make-sparse-keymap)))
80 | ;; TODO add good default keys here
81 | ;;(define-key map (kbd "C-c f") 'insert-foo)
82 | map)
83 | "Keymap for omnisharp-mode.")
84 |
85 | (defcustom omnisharp-cache-directory (f-join (locate-user-emacs-file ".cache") "omnisharp")
86 | "Directory to store files that omnisharp produces."
87 | :group 'omnisharp
88 | :type 'directory)
89 |
90 | (defcustom omnisharp-server-executable-path nil
91 | "Path to OmniSharp server override. Should be set to non-nil if server is installed locally.
92 | Otherwise omnisharp request the user to do M-x `omnisharp-install-server` and that server
93 | executable will be used instead."
94 | :type '(choice (const :tag "Not Set" nil) string))
95 |
96 | (defcustom omnisharp-expected-server-version "1.37.13"
97 | "Version of the omnisharp-roslyn server that this omnisharp-emacs package
98 | is built for. Also used to select version for automatic server installation."
99 | :group 'omnisharp
100 | :type 'string)
101 |
102 | (defcustom omnisharp-auto-complete-popup-help-delay nil
103 | "The timeout after which the auto-complete popup will show its help
104 | popup. Disabled by default because the help is often scrambled and
105 | looks bad."
106 | :group 'omnisharp
107 | :type '(choice (const :tag "disabled" nil)
108 | integer))
109 |
110 | (defcustom omnisharp-auto-complete-popup-persist-help t
111 | "Whether to keep the help window (accessed by pressing f1 while the
112 | popup window is active) open after any other key is
113 | pressed. Defaults to true."
114 | :group 'omnisharp
115 | :type '(choice (const :tag "Yes" t)
116 | (const :tag "No" nil)))
117 |
118 | (defcustom omnisharp-auto-complete-want-documentation t
119 | "Whether to include auto-complete documentation for each and every
120 | response. This may be set to nil to get a speed boost for
121 | completions."
122 | :group 'omnisharp
123 | :type '(choice (const :tag "Yes" t)
124 | (const :tag "No" nil)))
125 |
126 | (defcustom omnisharp-auto-complete-want-importable-types nil
127 | "Whether to search for autocompletions in all available
128 | namespaces. If a match is found for a new namespace, the namespace is
129 | automatically imported. This variable may be set to nil to get a speed
130 | boost for completions."
131 | :group 'omnisharp
132 | :type '(choice (const :tag "Yes" t)
133 | (const :tag "No" nil)))
134 |
135 | (defcustom omnisharp-company-do-template-completion t
136 | "Set to t if you want in-line parameter completion, nil
137 | otherwise."
138 | :group 'omnisharp
139 | :type '(choice (const :tag "Yes" t)
140 | (const :tag "No" nil)))
141 |
142 | (defcustom omnisharp-company-template-use-yasnippet t
143 | "Set to t if you want completion to happen via yasnippet
144 | otherwise fall back on company's templating. Requires yasnippet
145 | to be installed"
146 |
147 | :group 'omnisharp
148 | :type '(choice (const :tag "Yes" t)
149 | (const :tag "No" nil)))
150 |
151 | (defcustom omnisharp-company-ignore-case t
152 | "If t, case is ignored in completion matches."
153 | :group 'omnisharp
154 | :type '(choice (const :tag "Yes" t)
155 | (const :tag "No" nil)))
156 |
157 | (defcustom omnisharp-company-strip-trailing-brackets nil
158 | "If t, strips trailing <> and () from completions."
159 | :group 'omnisharp
160 | :type '(choice (const :tag "Yes" t)
161 | (const :tag "No" nil)))
162 |
163 | (defcustom omnisharp-company-begin-after-member-access t
164 | "If t, begin completion when pressing '.' after a class, object
165 | or namespace"
166 | :group 'omnisharp
167 | :type '(choice (const :tag "Yes" t)
168 | (const :tag "No" nil)))
169 |
170 | (defcustom omnisharp-company-sort-results t
171 | "If t, autocompletion results are sorted alphabetically"
172 | :group 'omnisharp
173 | :type '(choice (const :tag "Yes" t)
174 | (const :tag "No" nil)))
175 |
176 | (defcustom omnisharp-imenu-support nil
177 | "If t, activate imenu integration. Defaults to nil."
178 | :group 'omnisharp
179 | :type '(choice (const :tag "Yes" t)
180 | (const :tag "No" nil)))
181 |
182 | (defcustom omnisharp-eldoc-support t
183 | "If t, activate eldoc integration - eldoc-mode must also be enabled for
184 | this to work. Defaults to t."
185 | :group 'omnisharp
186 | :type '(choice (const :tag "Yes" t)
187 | (const :tag "No" nil)))
188 |
189 | (defcustom omnisharp-company-match-type 'company-match-simple
190 | "Simple defaults to company's normal prefix matching (fast).
191 | Server allows the omnisharp-server to do the matching (slow but does fuzzy matching)."
192 | :group 'omnisharp
193 | :type '(choice (const :tag "Simple" 'company-match-simple)
194 | (const :tag "Server" 'company-match-server)))
195 |
196 | ;; auto-complete-mode integration
197 | (defcustom omnisharp-auto-complete-template-use-yasnippet t
198 | "Set to t if you want completion to happen via yasnippet
199 | otherwise fall back on auto-complete's templating. Requires yasnippet
200 | to be installed"
201 |
202 | :group 'omnisharp
203 | :type '(choice (const :tag "Yes" t)
204 | (const :tag "No" nil)))
205 |
206 | (defcustom omnisharp-completing-read-function 'omnisharp-builtin-completing-read
207 | "Function to be called when requesting input from the user."
208 | :group 'omnisharp
209 | :type '(radio (function-item omnisharp-builtin-completing-read)
210 | (function-item ido-completing-read)
211 | (function-item ivy-completing-read)
212 | (function-item helm--completing-read-default)
213 | (function :tag "Other function")))
214 |
215 | (provide 'omnisharp-settings)
216 |
--------------------------------------------------------------------------------
/omnisharp-navigation-actions.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 |
17 | (require 'dash)
18 |
19 | (defun omnisharp-go-to-definition (&optional other-window)
20 | "Jump to the definition of the symbol under point. With prefix
21 | argument, use another window."
22 | (interactive "P")
23 | (let ((gotodefinition-request (append
24 | '((WantMetadata . t))
25 | (omnisharp--get-request-object))))
26 | (omnisharp--send-command-to-server
27 | "gotodefinition"
28 | gotodefinition-request
29 | (lambda (response)
30 | (omnisharp--prepare-metadata-buffer-if-needed
31 | (omnisharp--get-filename response)
32 | (cdr (assoc 'MetadataSource response))
33 | (lambda (buffer filename)
34 | (omnisharp-go-to-file-line-and-column response
35 | other-window
36 | buffer)))))))
37 |
38 | (defun omnisharp--prepare-metadata-buffer-if-needed (filename
39 | metadata-source
40 | callback)
41 | "Prepares metadata buffer if required (if FILENAME is missing and
42 | METADATA-SOURCE is available) and then invokes CALLBACK with either
43 | buffer or FILENAME of the file containing the definition.
44 |
45 | Metadata buffer is made readonly and both omnisharp-mode and csharp-mode's
46 | are enabled on this buffer."
47 | (cond
48 | ;; when gotodefinition returns FileName for the same
49 | ;; metadata buffer as we're in:
50 | ;; just return current buffer
51 | ((and (boundp 'omnisharp--metadata-source)
52 | (string-equal filename omnisharp--metadata-source))
53 | (funcall callback (current-buffer) nil))
54 |
55 | ;; when gotodefinition returns an actual filename on the filesystem:
56 | ;; navigate to this file
57 | (filename
58 | (funcall callback nil filename))
59 |
60 | ;; when gotodefinition returns metadata reference:
61 | ;; in this case we need to invoke /metadata endpoint to fetch
62 | ;; generated C# source for this type from the server (unless we
63 | ;; have it already in an existing buffer)
64 | (metadata-source
65 | (let* ((metadata-buffer-name (omnisharp--make-metadata-buffer-name
66 | metadata-source))
67 | (existing-metadata-buffer (get-buffer metadata-buffer-name)))
68 | (if existing-metadata-buffer
69 | ;; ok, we have this buffer for this metadata source loaded already
70 | (funcall callback existing-metadata-buffer nil)
71 |
72 | ;; otherwise we need to actually retrieve metadata-generated source
73 | ;; and create a buffer for this type
74 | (omnisharp--send-command-to-server
75 | "metadata"
76 | metadata-source
77 | (lambda (response)
78 | (let ((source (cdr (assoc 'Source response)))
79 | (source-name (cdr (assoc 'SourceName response)))
80 | (new-metadata-buffer (get-buffer-create metadata-buffer-name)))
81 | (with-current-buffer new-metadata-buffer
82 | (insert source)
83 | (csharp-mode)
84 | (omnisharp-mode)
85 | (setq-local omnisharp--metadata-source source-name)
86 | (toggle-read-only 1))
87 | (funcall callback new-metadata-buffer nil)))))))
88 | (t
89 | (message
90 | "Cannot go to definition as none was returned by the API."))))
91 |
92 | (defun omnisharp--make-metadata-buffer-name (metadata-source)
93 | "Builds unique buffer name for the given MetadataSource object.
94 | This buffer name assumed to be stable and unique."
95 |
96 | (let ((assembly-name (cdr (assoc 'AssemblyName metadata-source)))
97 | (type-name (cdr (assoc 'TypeName metadata-source)))
98 | (project-name (cdr (assoc 'ProjectName metadata-source))))
99 | (concat "*omnisharp-metadata:" project-name ":" assembly-name ":" type-name "*")))
100 |
101 | (defun omnisharp-go-to-definition-other-window ()
102 | "Do `omnisharp-go-to-definition' displaying the result in a different window."
103 | (interactive)
104 | (omnisharp-go-to-definition t))
105 |
106 | (defun omnisharp-navigate-to-current-file-member
107 | (&optional other-window)
108 | "Show a list of all members in the current file, and jump to the
109 | selected member. With prefix argument, use another window."
110 | (interactive "P")
111 | (omnisharp--send-command-to-server
112 | "currentfilemembersasflat"
113 | (omnisharp--get-request-object)
114 | (lambda (quickfixes)
115 | (omnisharp--choose-and-go-to-quickfix-ido
116 | quickfixes
117 | other-window))))
118 |
119 | (defun omnisharp-navigate-to-current-file-member-other-window ()
120 | (interactive)
121 | (omnisharp-navigate-to-current-file-member t))
122 |
123 | (defun omnisharp--choose-and-go-to-quickfix-ido
124 | (quickfixes &optional other-window)
125 | "Given a list of QuickFixes in list format (not JSON), displays them
126 | in an completing-read prompt and jumps to the chosen one's
127 | Location.
128 |
129 | If OTHER-WINDOW is given, will jump to the result in another window."
130 | (let ((chosen-quickfix
131 | (omnisharp--choose-quickfix-ido
132 | (omnisharp--vector-to-list quickfixes))))
133 | (omnisharp-go-to-file-line-and-column chosen-quickfix
134 | other-window)))
135 |
136 | (defun omnisharp--choose-quickfix-ido (quickfixes)
137 | "Given a list of QuickFixes, lets the user choose one using
138 | completing-read. Returns the chosen element."
139 | ;; Ido cannot navigate non-unique items reliably. It either gets
140 | ;; stuck, or results in that we cannot reliably determine the index
141 | ;; of the item. Work around this by prepending the index of all items
142 | ;; to their end. This makes them unique.
143 | (let* ((quickfix-choices
144 | (--map-indexed
145 | (let ((this-quickfix-text (cdr (assoc 'Text it))))
146 | (concat "#"
147 | (number-to-string it-index)
148 | "\t"
149 | this-quickfix-text))
150 |
151 | quickfixes))
152 |
153 | (chosen-quickfix-text
154 | (omnisharp--completing-read
155 | "Go to: "
156 | ;; TODO use a hashmap if too slow.
157 | ;; This algorithm is two iterations in the worst case
158 | ;; scenario.
159 | quickfix-choices))
160 | (chosen-quickfix-index
161 | (cl-position-if (lambda (quickfix-text)
162 | (equal quickfix-text chosen-quickfix-text))
163 | quickfix-choices)))
164 | (nth chosen-quickfix-index quickfixes)))
165 |
166 | (defun omnisharp-navigate-to-solution-member (&optional other-window)
167 | (interactive "P")
168 | (let ((filter (omnisharp--read-string
169 | "Enter the start of the symbol to go to: ")))
170 | (omnisharp--send-command-to-server
171 | "findsymbols"
172 | ;; gets all symbols. could also filter here but ido doesn't play
173 | ;; well with changing its choices
174 | `((Filter . ,filter))
175 | (-lambda ((&alist 'QuickFixes quickfixes))
176 | (omnisharp--choose-and-go-to-quickfix-ido quickfixes other-window)))))
177 |
178 | (defun omnisharp-navigate-to-solution-member-other-window ()
179 | (omnisharp-navigate-to-solution-member t))
180 |
181 | (defun omnisharp-navigate-to-solution-file (&optional other-window)
182 | (interactive "P")
183 | (omnisharp--send-command-to-server
184 | "gotofile"
185 | nil
186 | (-lambda ((&alist 'QuickFixes quickfixes))
187 | (omnisharp--choose-and-go-to-quickfix-ido quickfixes other-window))))
188 |
189 | (defun omnisharp-navigate-to-solution-file-then-file-member
190 | (&optional other-window)
191 | "Navigates to a file in the solution first, then to a member in that
192 | file. With prefix argument uses another window."
193 | (interactive "P")
194 | (omnisharp-navigate-to-solution-file other-window)
195 | ;; Do not set other-window here. No need to use two different
196 | ;; windows.
197 | (omnisharp-navigate-to-current-file-member))
198 |
199 | (defun omnisharp-navigate-to-solution-file-then-file-member-other-window
200 | (&optional other-window)
201 | (omnisharp-navigate-to-solution-file-then-file-member t))
202 |
203 | (defun omnisharp-navigate-to-region
204 | (&optional other-window)
205 | "Navigate to region in current file. If OTHER-WINDOW is given and t,
206 | use another window."
207 | (interactive "P")
208 | (omnisharp--send-command-to-server
209 | "gotoregion"
210 | (omnisharp--get-request-object)
211 | (-lambda ((&alist 'QuickFixes quickfixes))
212 | (omnisharp--choose-and-go-to-quickfix-ido quickfixes other-window))))
213 |
214 | (provide 'omnisharp-navigation-actions)
215 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Depreciation notice
2 |
3 | `omnisharp-emacs` is being depreciated in favor of LSP-flavoured clients and is
4 | not being actively developed. There are a couple of LSP clients that you can use
5 | in emacs that communicate over LSP with `omnisharp-roslyn` server:
6 | - [lsp-mode](https://github.com/emacs-lsp/lsp-mode) and
7 | - [eglot](https://github.com/joaotavora/eglot).
8 |
9 | Some of the features of omnisharp-emacs have not been ported to LSP yet, however, like:
10 | - assembly introspection (ability to jump to a definition imported from a .dll).
11 |
12 | # omnisharp-emacs
13 |
14 | [](https://melpa.org/#/omnisharp)
15 | [](https://stable.melpa.org/#/omnisharp)
16 |
17 | omnisharp-emacs is a port of the awesome [omnisharp-roslyn][] server to the
18 | Emacs text editor. It provides IDE-like features for editing files in
19 | C# solutions in Emacs, provided by an OmniSharp server instance that
20 | works in the background.
21 |
22 | Note that C# syntax highlighting and indenting is provided by [`csharp-mode`](https://github.com/josteink/csharp-mode) which is a dependency of this
23 | package. See [Configuration](#configuration) section below on how to enable
24 | `omnisharp-mode` via the `csharp-mode` hook.
25 |
26 | This package is licensed under GNU General Public License version 3,
27 | or (at your option) any later version.
28 |
29 |
30 | ## Features
31 | Please see [omnisharp-emacs Features](doc/features.md). Please note that information
32 | on the Features page is outdated and some commands are not ported to the new roslyn
33 | version of `omnisharp-emacs` yet.
34 |
35 |
36 | ## Package Installation
37 | This package requires Emacs 24.4 and above. It has been tested on
38 | Ubuntu, Windows 7+ and on macOS.
39 |
40 |
41 | ### External Dependencies
42 | You may need to have one or more of .NET SDKs (and mono – on UNIX platforms)
43 | installed for your project to be properly processed by omnisharp server.
44 |
45 | Note that multiple .NET SDKs can be installed in parallel, too.
46 |
47 | See:
48 | - [.NET (Core) SDKs](https://www.microsoft.com/net/targeting)
49 | - [mono project](http://www.mono-project.com/download/)
50 |
51 |
52 | ### Installation on Spacemacs
53 | Add `csharp` layer to `dotspacemacs-configuration-layers` on your `.spacemacs`
54 | file and restart Emacs or run `dotspacemacs/sync-configuration-layers` (`SPC f e R`
55 | in evil mode, `M-m f e R` in Emacs mode).
56 | `csharp-mode` and `omnisharp` packages will get installed automatically for you
57 | as the configuration is reloaded.
58 |
59 |
60 | ### Installation on Regular Emacs
61 | To install, use [MELPA][].
62 | After MELPA is configured correctly, use
63 |
64 |
65 | M-x package-refresh-contents RET
66 | M-x package-install RET omnisharp RET
67 |
68 | to install.
69 |
70 | When installing the `omnisharp` package `package.el` will also
71 | automatically pull in `csharp-mode` for you as well.
72 |
73 | You can also add
74 | ```
75 | (package-install 'omnisharp)
76 | ```
77 | to your `init.el` to force installation of `omnisharp-emacs` on
78 | every install.
79 |
80 |
81 | ## Configuration
82 | This section describes an example configuration for `omnisharp-emacs`.
83 | This is not required if you are using spacemacs unless you want to
84 | override existing bindings, etc.
85 |
86 | ### Applying omnisharp-mode and auto-starting server when visiting C# files
87 | Add this to `csharp-mode-hook` to your `init.el` to automatically invoke
88 | `omnisharp-emacs` when opening C# files:
89 | ```
90 | (add-hook 'csharp-mode-hook 'omnisharp-mode)
91 | ```
92 |
93 | `omnisharp-emacs` will attempt to start a server automatically for you when
94 | opening a .cs file (if a server has not been started already). Otherwise, you
95 | will need to start the server with `M-x omnisharp-start-omnisharp-server RET`
96 | should it fail to find a .sln file or project root (via projectile). It will
97 | prompt you for a solution file or project root directory you want to work
98 | with.
99 |
100 | ### Autocompletion
101 | For autocompletion via [company](https://github.com/company-mode/company-mode)
102 | mode to work you will also need this in your `init.el`:
103 | ```
104 | (eval-after-load
105 | 'company
106 | '(add-to-list 'company-backends 'company-omnisharp))
107 |
108 | (add-hook 'csharp-mode-hook #'company-mode)
109 | ```
110 |
111 | Also, for company completion to work you need to install `company` from
112 | [melpa](https://melpa.org/#/company).
113 |
114 | ### Flycheck
115 | `omnisharp-emacs` supports [Flycheck](https://github.com/flycheck/flycheck)
116 | and it can be enabled automatically by hooking up `flycheck-mode` to be enabled
117 | for `csharp-mode` buffers:
118 |
119 | ```
120 | (add-hook 'csharp-mode-hook #'flycheck-mode)
121 | ```
122 |
123 | ### Combined setup example
124 |
125 | This is an example code that will enable `company-mode` and `flycheck-mode`
126 | and will set some formatting variables for `csharp-mode`. Also, it shows how
127 | to setup keybindings for `csharp-mode`.
128 |
129 | ```
130 | (eval-after-load
131 | 'company
132 | '(add-to-list 'company-backends #'company-omnisharp))
133 |
134 | (defun my-csharp-mode-setup ()
135 | (omnisharp-mode)
136 | (company-mode)
137 | (flycheck-mode)
138 |
139 | (setq indent-tabs-mode nil)
140 | (setq c-syntactic-indentation t)
141 | (c-set-style "ellemtel")
142 | (setq c-basic-offset 4)
143 | (setq truncate-lines t)
144 | (setq tab-width 4)
145 | (setq evil-shift-width 4)
146 |
147 | ;csharp-mode README.md recommends this too
148 | ;(electric-pair-mode 1) ;; Emacs 24
149 | ;(electric-pair-local-mode 1) ;; Emacs 25
150 |
151 | (local-set-key (kbd "C-c r r") 'omnisharp-run-code-action-refactoring)
152 | (local-set-key (kbd "C-c C-c") 'recompile))
153 |
154 | (add-hook 'csharp-mode-hook 'my-csharp-mode-setup t)
155 | ```
156 |
157 | There is also an example configuration for evil-mode included in the project,
158 | please see `doc/example-config-for-evil-mode.el`.
159 |
160 |
161 | ## Server Installation
162 | This emacs package requires the [omnisharp-roslyn][] server program.
163 | Emacs will manage connection to the server as a subprocess.
164 |
165 | The easiest/default way to install the server is to invoke
166 | `M-x omnisharp-install-server` and follow instructions on minibuffer.
167 |
168 | If that fails (or you feel adventurous) please see
169 | [installing omnisharp server](doc/server-installation.md) on how to install the
170 | server manually.
171 |
172 |
173 | ## Troubleshooting
174 | Most of the time (if the server has been installed properly) you can diagnose
175 | issues by looking at the `*omnisharp-log*` buffer where `omnisharp-emacs` emits
176 | any log messages from the omnisharp server.
177 |
178 |
179 | ### macOS: Mono.framework not on $PATH
180 | Some projects may fail to load in omnisharp-server when Mono.framework is not
181 | on $PATH or $PATH is not picked up by emacs.
182 |
183 | An example output in *omnisharp-log* is:
184 | ```
185 | [12:23:33] ERROR: OmniSharp.MSBuild.ProjectFile.ProjectFileInfo, The reference assemblies for
186 | framework ".NETFramework,Version=v3.5" were not found. To resolve this, install the SDK or
187 | Targeting Pack for this framework version or retarget your application to a version of the framework
188 | for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from
189 | the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your
190 | assembly may not be correctly targeted for the framework you intend.
191 | ```
192 |
193 | See [issue #426](https://github.com/OmniSharp/omnisharp-emacs/issues/426).
194 |
195 | ### Linux: reference assemblies not found
196 |
197 | If you see
198 |
199 | ```
200 | [13:04:01] ERROR: OmniSharp.MSBuild.ProjectFile.ProjectFileInfo, The reference assemblies for framework ".NETFramework,Version=v4.5.1" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend.
201 | ```
202 |
203 | then add the official Mono repository by following the instructions on
204 | https://www.mono-project.com/download/stable/ and install the package
205 | `mono-complete`.
206 |
207 | On Debian-based systems:
208 |
209 | sudo apt install --no-install-recommends mono-complete
210 |
211 | On Fedora:
212 |
213 | sudo dnf install mono-complete
214 |
215 | On CentOS:
216 |
217 | sudo yum install mono-complete
218 |
219 | You'll need this even if you've installed the official Microsoft Linux
220 | packages (`dotnet` etc.).
221 |
222 |
223 | ### Missing .NET SDKs
224 | You may find that your project can not be loaded when .NET SDK is not installed
225 | on your machine.
226 |
227 | A log line indicating the problem would look like this on `*omnisharp-log*`:
228 | ```
229 | [19:24:59] WARNING: Microsoft.Build.Exceptions.InvalidProjectFileException: The SDK 'Microsoft.NET.Sdk' specified could not be found. ...
230 | ```
231 |
232 |
233 | ### Error loading csproj file
234 | You may encounter an issue where omnisharp server fails to load a project, this looks like on `*omnisharp-log*`:
235 | ```
236 | [22:46:22] WARNING: OmniSharp.MSBuild.MSBuildProjectSystem, Failed to load project file '/Users/{user}/temp/temp.csproj'.
237 | ```
238 |
239 | To fix this, on Linux, you may need to install the `msbuild-stable` package.
240 |
241 | This issue and a fix has been reported on [issue #430](https://github.com/OmniSharp/omnisharp-emacs/issues/430).
242 |
243 |
244 | ### Server opened in a different program (e.g. wine), instead of mono.
245 | On Linux, it's possible for the plugin to open the server binary, OmniSharp.exe, in a different program than mono, due to [binary format rules](https://en.wikipedia.org/wiki/Binfmt_misc). OmniSharp.exe needs to be passed to mono, but a binfmt rule might override that.
246 |
247 | In the case of wine being used to run OmniSharp.exe, the plugin might trigger a wine desktop to appear, if the prefix is set to emulate one. Additionally, the plugin will issue several errors like this:
248 | ```
249 | omnisharp--handle-server-message error: (wrong-type-argument listp 0). See the OmniServer process buffer for detailed server output.
250 | ```
251 |
252 | Different distros may manage binfmt a bit differently. To fix this, either consult distro specific documentation and find how to remove the offending rule or set `omnisharp-server-executable-path` to a shell script that explictly calls mono:
253 | ```sh
254 | #!/bin/sh
255 | exec mono "[path to omnisharp]/OmniSharp.exe" "$@"
256 | ```
257 |
258 | This issue and workarounds for the Arch+Wine case have been reported on [issue #477](https://github.com/OmniSharp/omnisharp-emacs/issues/477).
259 |
260 | ## Contributing
261 |
262 | ### How to run tests
263 |
264 | You can run all kind of tests by following shell script.
265 |
266 | ```sh
267 | ./run-tests.sh
268 | ```
269 |
270 | * * * * *
271 |
272 | Pull requests welcome!
273 |
274 | [omnisharp-roslyn]: https://github.com/OmniSharp/omnisharp-roslyn
275 | [popup.el]: https://github.com/auto-complete/popup-el
276 | [company-mode]: http://company-mode.github.io
277 | [ido-mode]: http://www.emacswiki.org/emacs/InteractivelyDoThings
278 | [Flycheck]: https://github.com/lunaryorn/flycheck
279 | [MELPA]: https://github.com/milkypostman/melpa/#usage
280 |
--------------------------------------------------------------------------------
/test/unit-test.el:
--------------------------------------------------------------------------------
1 |
2 | ;; This file is free software; you can redistribute it and/or modify
3 | ;; it under the terms of the GNU General Public License as published by
4 | ;; the Free Software Foundation; either version 3, or (at your option)
5 | ;; any later version.
6 |
7 | ;; This file is distributed in the hope that it will be useful,
8 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | ;; GNU General Public License for more details.
11 |
12 | ;; You should have received a copy of the GNU General Public License
13 | ;; along with this program. If not, see .
14 |
15 |
16 | ;;
17 | ;; You can run tests with M-x ert but remember to evaluate them before
18 | ;; running if you changed something!
19 |
20 | (require 'el-mock)
21 | (require 'noflet)
22 |
23 | (ert-deftest omnisharp--get-omnisharp-server-executable-command ()
24 | "The correct server path must be returned on windows and unix systems"
25 |
26 | ;; Windows
27 | ;;
28 | ;; ignore expand-file-name calls. just return the original to keep
29 | ;; things maintainable
30 | (noflet ((expand-file-name (file-name &rest _args)
31 | file-name))
32 | (with-mock
33 | (setq omnisharp-server-executable-path "OmniSharp.exe")
34 | (stub w32-shell-dos-semantics)
35 | (should
36 | (equal '("OmniSharp.exe" "-s" "some solution.sln")
37 | (let ((system-type 'windows-nt))
38 | (omnisharp--get-omnisharp-server-executable-command
39 | "some solution.sln")))))
40 |
41 | ;; osx
42 | (let ((system-type 'darwin))
43 | (should
44 | (equal '("mono" "OmniSharp.exe" "-s" "some solution.sln")
45 | (omnisharp--get-omnisharp-server-executable-command
46 | "some solution.sln"))))
47 |
48 | ;; linux
49 | (let ((system-type 'gnu/linux))
50 | (should
51 | (equal '("mono" "OmniSharp.exe" "-s" "some solution.sln")
52 | (omnisharp--get-omnisharp-server-executable-command
53 | "some solution.sln")))
54 |
55 | ;; Should also support an optional parameter
56 | (should
57 | (equal '("mono" "/another/path/to/OmniSharp.exe" "-s" "some solution.sln")
58 | (omnisharp--get-omnisharp-server-executable-command
59 | "some solution.sln"
60 | "/another/path/to/OmniSharp.exe"))))))
61 |
62 | (defmacro with-test-buffer-contents (buffer-contents
63 | code-to-run-in-buffer)
64 | `(with-current-buffer (get-buffer-create "omnisharp-test-buffer")
65 | (switch-to-buffer (get-buffer-create "omnisharp-test-buffer"))
66 | (delete-region (point-min) (point-max))
67 | (--map (insert (concat it "\n")) ,buffer-contents)
68 | (beginning-of-buffer)
69 |
70 | ,code-to-run-in-buffer))
71 |
72 | (defmacro with-active-region-in-buffer (buffer-contents
73 | code-to-run-in-buffer)
74 | "Run CODE-TO-RUN-IN-BUFFER in a temp bufer with BUFFER-CONTENTS, and
75 | the region active between the markers region-starts-here and
76 | region-ends-here."
77 | `(with-test-buffer-contents
78 | ,buffer-contents
79 |
80 | (progn
81 | (evil-exit-visual-state)
82 | ;; remove region-starts-here markers
83 | (re-search-forward "(region-starts-here)")
84 | (replace-match "")
85 | (setq region-start (point))
86 | (re-search-forward "(region-ends-here)")
87 | (replace-match "")
88 |
89 | ;; select the text between the current position and the last one
90 | (push-mark region-start)
91 | (activate-mark)
92 |
93 | ,code-to-run-in-buffer)))
94 |
95 | ;; Region line and column helper tests.
96 | ;; Could be one test but on the other hand this way we know if only
97 | ;; one of them fails.
98 | (ert-deftest omnisharp--region-start-line-reports-correct-line ()
99 | (with-active-region-in-buffer
100 | ;; These are multiple lines because my emacs started to mess up the
101 | ;; syntax highlighting if they were one long multiline string. And
102 | ;; it's difficult to reason about columns when all lines have
103 | ;; leading whitespace
104 | '("line 1"
105 | "lin(region-starts-here)e 2"
106 | "line 3"
107 | "line 4"
108 | "(region-ends-here)line 5")
109 | (should (equal 2
110 | (omnisharp--region-start-line)))))
111 |
112 | (ert-deftest omnisharp--region-end-line-reports-correct-line ()
113 | (with-active-region-in-buffer
114 | '("line 1"
115 | "lin(region-starts-here)e 2"
116 | "line 3"
117 | "line 4"
118 | "(region-ends-here)line 5")
119 | (should (equal 5
120 | (omnisharp--region-end-line)))))
121 |
122 | (ert-deftest omnisharp--region-start-column-reports-correct-column ()
123 | (with-active-region-in-buffer
124 | '("line 1"
125 | "lin(region-starts-here)e 2"
126 | "line 3"
127 | "line 4"
128 | "(region-ends-here)line 5")
129 | (should (equal 4
130 | (omnisharp--region-start-column)))))
131 |
132 | (ert-deftest omnisharp--region-end-column-reports-correct-column ()
133 | (with-active-region-in-buffer
134 | '("line 1"
135 | "lin(region-starts-here)e 2"
136 | "line 3"
137 | "line 4"
138 | "(region-ends-here)line 5")
139 | (should (equal 1 (omnisharp--region-end-column)))))
140 |
141 | (ert-deftest omnisharp--region-start-column-with-evil-mode-line-selection ()
142 | (with-current-buffer (get-buffer-create "omnisharp-test-buffer")
143 | (erase-buffer)
144 | (insert "This is a line with a length of 34\n") ; this is tested
145 | (insert "Another line.\n")
146 | (insert "There is a bug that doesn't occur with just one line.\n")
147 | (evil-visual-line
148 | (progn (goto-line 1) (point))
149 | (progn (goto-line 2) (point)))
150 |
151 | (should (equal 1 (omnisharp--region-start-line)))
152 | (should (equal 2 (omnisharp--region-end-line)))
153 | (should (equal 1 (omnisharp--region-start-column)))
154 | (should (equal 14 (omnisharp--region-end-column)))))
155 |
156 | (ert-deftest omnisharp--go-to-end-of-region-with-evil-selection-test ()
157 | (with-current-buffer (get-buffer-create "omnisharp-test-buffer")
158 | (evil-exit-visual-state)
159 | (erase-buffer)
160 | (insert "This is a line with a length of 34\n")
161 | (insert "Another line")
162 | (goto-line 1)
163 |
164 | (evil-visual-select 0 5)
165 |
166 | (omnisharp--goto-end-of-region)
167 |
168 | (should (equal 1 (omnisharp--region-start-line)))
169 | (should (equal 1 (omnisharp--region-end-line)))
170 | (should (equal 1 (omnisharp--region-start-column)))
171 | (should (equal 5 (omnisharp--region-end-column)))))
172 |
173 | (ert-deftest omnisharp--go-to-end-of-region-with-evil-line-selection-test ()
174 | (with-current-buffer (get-buffer-create "omnisharp-test-buffer")
175 | (evil-exit-visual-state)
176 | (erase-buffer)
177 | (insert "This is a line with a length of 34\n")
178 | (insert "Another line")
179 | (goto-line 1)
180 | (evil-visual-line)
181 |
182 | (omnisharp--goto-end-of-region)
183 |
184 | (should (equal 1 (omnisharp--region-start-line)))
185 | (should (equal 1 (omnisharp--region-end-line)))
186 | (should (equal 1 (omnisharp--region-start-column)))
187 | (should (equal 35 (omnisharp--region-end-column)))))
188 |
189 | (defun get-line-text (&optional line-number)
190 | "Returns the text on the current line or another line with the
191 | number given"
192 | (when (equal nil line-number)
193 | (setq line-number (line-number-at-pos)))
194 | (goto-line line-number)
195 | (buffer-substring-no-properties
196 | (line-beginning-position)
197 | (line-end-position)))
198 |
199 | (ert-deftest omnisharp--insert-namespace-import ()
200 | (let* ((new-import "System.IO"))
201 | (with-temp-buffer
202 | (-each '("using System;\n"
203 | "\n"
204 | "public class Awesome {}")
205 | 'insert)
206 | (omnisharp--insert-namespace-import new-import)
207 |
208 | (should (equal (concat "using " new-import ";")
209 | (get-line-text 0))))))
210 |
211 | (ert-deftest activating-omnisharp-mode-should-not-start-server-if-running ()
212 | "When server is already running, a new server should not be started"
213 | (with-mock
214 | (stub omnisharp--check-alive-status-worker => t)
215 | (stub omnisharp-start-omnisharp-server)
216 | (not-called start-process)
217 | (not-called omnisharp--find-solution-file)
218 | (omnisharp-mode)))
219 |
220 | (ert-deftest omnisharp--write-quickfixes-to-compilation-buffer--has-expected-contents ()
221 | "Writing QuickFixes to the compilation buffer should have the
222 | expected output in that buffer"
223 | (save-excursion
224 | (let ((buffer-name "test-buffer-name")
225 | (quickfixes-to-write
226 | '(((Text . "public class MyClass")
227 | (EndColumn . 0)
228 | (EndLine . 0)
229 | (Column . 18)
230 | (Line . 5)
231 | (FileName . "/project/MyClass.cs")
232 | (LogLevel . nil)))))
233 |
234 | (omnisharp--write-quickfixes-to-compilation-buffer
235 | quickfixes-to-write
236 | buffer-name
237 | "test-buffer-header\n\n")
238 | (switch-to-buffer buffer-name)
239 |
240 | (let ((contents (buffer-string)))
241 | (should (s-contains? "test-buffer-header" contents))
242 | (should (s-contains? "/project/MyClass.cs:5:18:" contents))
243 | (should (s-contains? "public class MyClass" contents))))))
244 |
245 |
246 | (ert-deftest
247 | omnisharp--write-quickfixes-to-compilation-buffer-doesnt-mess-with-find-tag-marker-ring ()
248 |
249 | (with-mock
250 | (stub ring-insert => (error "must not be called"))
251 | (save-excursion
252 | (omnisharp--write-quickfixes-to-compilation-buffer
253 | '()
254 | "buffer-name"
255 | "test-buffer-header\n\n"
256 | ;; don't save old position to find-tag-marker-ring
257 | t))))
258 |
259 | (ert-deftest omnisharp--convert-auto-complete-result-to-popup-format-shows-correct-data ()
260 | (let* ((description "Verbosity Verbose; - description")
261 | (completion-text "Verbose - completion text")
262 | (snippet-text "Verbose$0")
263 | (auto-completions
264 | `[((Snippet . ,snippet-text)
265 | (ReturnType . "OmniSharp.Verbosity")
266 | (MethodHeader . nil)
267 | (RequiredNamespaceImport . nil)
268 | (DisplayText . "Verbosity Verbose - display text")
269 | (Description . ,description)
270 | (CompletionText . ,completion-text)
271 | (Kind . "Verbose"))])
272 | (converted-popup-item
273 | (nth 0
274 | (omnisharp--convert-auto-complete-result-to-popup-format
275 | auto-completions))))
276 |
277 | (should (equal description (popup-item-document converted-popup-item)))
278 | (should (equal completion-text (popup-item-value converted-popup-item)))
279 | (should (equal snippet-text (get-text-property 0 'Snippet (popup-item-value converted-popup-item))))
280 | ;; TODO figure out how to verify popup item DisplayText.
281 | ;; An item looked like this:
282 | ;; #("Verbosity Verbose - display text" 0 32 (document "Verbosity Verbose; - description" value "Verbose - completion text"))
283 | ))
284 |
285 | (ert-deftest omnisharp--apply-text-change-to-buffer-text ()
286 | (with-test-buffer-contents
287 | ["namespace testing {"
288 | " public class WillBeRenamed {}"
289 | "}"]
290 | (should (equal (progn
291 | (omnisharp--apply-text-change-to-buffer
292 | `((NewText . "NewClassName")
293 | (StartLine . 2) (EndLine . 2)
294 | (StartColumn . 18) (EndColumn . 31)))
295 | (omnisharp--get-current-buffer-contents))
296 | (s-join "\n"
297 | ["namespace testing {"
298 | " public class NewClassName {}"
299 | "}"
300 | ;; there is a trailing newline in the test
301 | ;; buffer too
302 | ""])))))
303 |
--------------------------------------------------------------------------------
/test/buttercup-tests/setup.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t -*-
2 | ;; License: GNU General Public License version 3, or (at your option) any later version
3 |
4 | ;;; This file is a common place for buttercup testing related
5 | ;;; utilities and initialization
6 |
7 | ;;; These are originally ported from old integration tests from legacy branch.
8 | ;;; It aims for very readable step definitions so that style is encouraged here too.
9 |
10 | (require 'f)
11 | (require 's)
12 |
13 | ;;; Work around for emacs bug#18845
14 | (when (and (= emacs-major-version 24) (>= emacs-minor-version 4))
15 | (require 'cl))
16 |
17 | ;;; These are displayed in the test output when a test opens a .cs
18 | ;;; file. Work around that by loading them in advance.
19 | (require 'csharp-mode)
20 | (require 'vc-git)
21 | (require 'el-mock)
22 |
23 | (defvar omnisharp-emacs-root-path
24 | (-> (f-this-file)
25 | f-parent
26 | f-parent
27 | f-parent))
28 |
29 | (defvar omnisharp-minimal-test-project-path
30 | (f-join omnisharp-emacs-root-path
31 | "test/MinimalProject"))
32 |
33 | (setq omnisharp-debug t)
34 | (print omnisharp-minimal-test-project-path)
35 | (add-to-list 'load-path omnisharp-emacs-root-path)
36 |
37 | ;;; the load-path has to contain omnisharp-emacs-root-path
38 | (--each (f-files omnisharp-emacs-root-path
39 | (lambda (file)
40 | (equal "el" (f-ext file))))
41 | (load-file it))
42 |
43 | (require 'omnisharp)
44 | (require 'buttercup)
45 |
46 | ;;; I grew tired of the omnisharp-- prefix so now I use ot--, standing
47 | ;;; for "omnisharp test"
48 | (defun ot--buffer-should-contain (&rest expected)
49 | (let ((expected (s-join "\n" expected))
50 | (actual (s-replace (string ?\C-m) (string ?\C-j)
51 | (substring-no-properties (buffer-string))))
52 | (message "Expected '%s' to be part of '%s', but was not. Actual contents are: '%s'"))
53 | (cl-assert (s-contains? expected actual) nil (format message expected actual (substring-no-properties (buffer-string))))))
54 |
55 | (defun ot--evaluate (command-to-execute)
56 | (eval (read command-to-execute)))
57 |
58 | (defun ot--evaluate-and-wait-for-server-response (command-to-execute)
59 | "NB: Will crash when calling a command that doesn't respond with a
60 | request id."
61 | (omnisharp--wait-until-request-completed
62 | (eval (read command-to-execute))))
63 |
64 | (defun ot--wait-for (predicate &optional timeout-seconds)
65 | (setq timeout-seconds (or timeout-seconds 2))
66 |
67 | (let ((start-time (current-time)))
68 | (while (not (funcall predicate))
69 | (when (> (cadr (time-subtract (current-time) start-time))
70 | timeout-seconds)
71 | (progn
72 | (let ((msg (format "Did not complete in %s seconds: %s"
73 | timeout-seconds
74 | (prin1-to-string predicate))))
75 | (error msg))))
76 | (accept-process-output nil 0.01))))
77 |
78 | (defun ot--switch-to-buffer (existing-buffer-name)
79 | (let ((buffer (get-buffer existing-buffer-name))
80 | (message "Expected the buffer %s to exist but it did not."))
81 | (cl-assert (not (eq nil buffer)) nil message existing-buffer-name)
82 | (switch-to-buffer buffer)))
83 |
84 | (defun ot--wait-for-seconds (seconds)
85 | (sit-for seconds))
86 |
87 | (defun ot--buffer-contents-and-point-at-$ (&rest buffer-contents-to-insert)
88 | "Test setup. Only works reliably if there is one $ character"
89 | (erase-buffer)
90 | (deactivate-mark)
91 | (--map (insert it "\n") buffer-contents-to-insert)
92 | (beginning-of-buffer)
93 | (search-forward "$")
94 | (delete-backward-char 1)
95 | ;; will block
96 | (omnisharp--update-buffer)
97 | (when (fboundp 'evil-insert)
98 | (evil-insert 1)))
99 |
100 | (defun ot--buffer-contents-and-region (&rest lines)
101 | "Notice: LINES have to contain $"
102 |
103 | ;; todo deactivate existing region
104 | (apply #'ot--buffer-contents-and-point-at-$ lines)
105 |
106 | (beginning-of-buffer)
107 |
108 | ;; remove region-starts-here markers
109 | (re-search-forward "(region-starts-here)")
110 | (replace-match "")
111 | (setq region-start (point))
112 | (re-search-forward "(region-ends-here)")
113 | (replace-match "")
114 |
115 | (omnisharp--update-buffer)
116 | ;; select the text between the current position and the last one
117 | (push-mark region-start)
118 | (activate-mark))
119 |
120 | (defun ot--point-should-be-on-line-number (expected-line-number)
121 | (let ((current-line-number (line-number-at-pos)))
122 | (cl-assert (= expected-line-number current-line-number)
123 | nil
124 | (concat
125 | "Expected point to be on line number '%s'"
126 | " but found it on '%s', the buffer containing:\n'%s'")
127 | expected-line-number
128 | current-line-number
129 | (buffer-string))))
130 |
131 | (defun ot--open-the-minimal-project-source-file (file-path-to-open)
132 | (when (get-buffer file-path-to-open)
133 | (kill-buffer file-path-to-open))
134 | (find-file (f-join omnisharp-minimal-test-project-path
135 | file-path-to-open))
136 | (setq buffer-read-only nil))
137 |
138 | (defun ot--delete-the-minimal-project-source-file (file-name)
139 | (-when-let (buffer (get-buffer file-name))
140 | (kill-buffer buffer))
141 | (let ((file-path (f-join omnisharp-minimal-test-project-path file-name)))
142 | (when (f-exists? file-path)
143 | (f-delete file-path))))
144 |
145 | (defun ot--point-should-be-on-a-line-containing (expected-line-contents)
146 | (let ((current-line (substring-no-properties (or (thing-at-point 'line) ""))))
147 | (cl-assert (s-contains? expected-line-contents current-line)
148 | nil
149 | (format
150 | (concat "Expected the current line (%d) to contain '%s'.\n"
151 | "The current buffer contains:\n%s\n"
152 | "The current line contains: '%s'")
153 | (line-number-at-pos)
154 | expected-line-contents
155 | (buffer-string)
156 | current-line))))
157 |
158 | (defun ot--there-should-be-a-window-editing-the-file (file-name)
159 | (cl-assert (get-buffer-window file-name)
160 | nil
161 | (concat
162 | "No visible window is editing the file '%s'."
163 | " Visible windows: '%s'")
164 | file-name
165 | (window-list)))
166 |
167 | (defun ot--switch-to-the-window-in-the-buffer (file-name)
168 | (select-window (get-buffer-window file-name)))
169 |
170 | (defun ot--i-should-be-in-buffer-name (expected-buffer-name)
171 | (cl-assert (equal (buffer-name)
172 | expected-buffer-name)
173 | nil
174 | (concat
175 | "Expected to be in buffer %s "
176 | "but was in buffer %s")
177 | expected-buffer-name
178 | (buffer-name)))
179 |
180 | (defun ot--i-should-see (&rest lines)
181 | (cl-assert (s-contains? (s-join "\n" lines)
182 | (buffer-string))
183 | nil
184 | (concat "Expected the buffer to contain '%s' but it did not. "
185 | "The buffer contains '%s'")
186 | lines
187 | (buffer-string)))
188 |
189 | ;; this is a poor man's version of action chains
190 | (defun ot--keyboard-input (&rest text-vectors)
191 | "Simulates typing. Can be used to do interactive input, but
192 | detecting situations in the middle of input is impossible."
193 | (condition-case error
194 | (execute-kbd-macro (cl-reduce 'vconcat text-vectors))
195 | (error (print (format "ot--keyboard-input error: %s" error)))))
196 |
197 | (defun ot--meta-x-command (command)
198 | (vconcat
199 | (ot--press-key "M-x")
200 | (ot--type command)
201 | (ot--press-key "RET")))
202 |
203 | (defun ot--type (text)
204 | (string-to-vector text))
205 |
206 | (defun ot--press-key (key-or-chord)
207 | (edmacro-parse-keys key-or-chord))
208 |
209 | (defun ot--get-completions ()
210 | (let* ((get-candidates-result (omnisharp--get-company-candidates ""))
211 | (fetcher (cdr get-candidates-result)))
212 |
213 | ;; omnisharp--get-company-candidates returns an :async callback,
214 | ;;; -- we need to invoke async machinery to get to the value of
215 | ;; omnisharp--last-buffer-specific-auto-complete-result
216 | (omnisharp--wait-until-request-completed (funcall fetcher (lambda (result) nil)))
217 |
218 | (-map (lambda(completion)
219 | (cdr (assoc 'DisplayText completion)))
220 | omnisharp--last-buffer-specific-auto-complete-result)
221 | ))
222 |
223 | (defmacro ot--set (symbol value)
224 | `(setq symbol ,value))
225 |
226 | (defmacro ot--answer-omnisharp--completing-read-with (answer-function)
227 | "Automatically select the first candidate given to
228 | omnisharp--completing-read. This could be done by controlling
229 | ido with the keyboard like in other tests, but ido is not easy to
230 | control programmatically.
231 |
232 | ANSWER-FUNCTION should receive a list of choices (strings) and respond
233 | with one."
234 | `(spy-on 'omnisharp--completing-read :and-call-fake
235 | (lambda (_prompt _quickfixes)
236 | (funcall ,answer-function _quickfixes))))
237 |
238 | (defun ot--wait-until-all-requests-completed (&optional timeout-seconds)
239 | (setq timeout-seconds (or timeout-seconds 2))
240 |
241 | (let ((start-time (current-time))
242 | (process (cdr (assoc :process omnisharp--server-info))))
243 | (while (cdr (assoc :response-handlers omnisharp--server-info))
244 | (when (> (cadr (time-subtract (current-time) start-time))
245 | timeout-seconds)
246 | (progn
247 | (let ((msg (format "All requests did not complete in %s seconds"
248 | timeout-seconds)))
249 | (omnisharp--log msg)
250 | (error msg))))
251 | (accept-process-output process 0.1))))
252 |
253 | ;; Test suite setup. Start a test server process that can be used by
254 | ;; all tests
255 |
256 | (print "trying to download and install omnisharp-roslyn server...")
257 | (omnisharp--install-server nil t)
258 |
259 | (print "trying to launch the server...")
260 | (omnisharp--do-server-start (s-concat omnisharp-emacs-root-path
261 | "/test/MinimalProject"))
262 |
263 | ;; wait that the server is alive and ready before starting the test run
264 | (with-timeout (2 ; seconds
265 | (omnisharp--log "Server did not start in time"))
266 | (while (not (equal t (cdr (assoc :started? omnisharp--server-info))))
267 | (accept-process-output)))
268 |
269 | ;; still sleep a bit because even with the input received the server
270 | ;; might still not be able to response to requests in-time for the
271 | ;; first test to run properly
272 | (print "waiting for the server to spin up (5 secs)..")
273 | (print (current-time-string))
274 |
275 | ;; sleep-for doesn't work in some versions of emacs. Using current-time-string
276 | ;; to ensure from output that we are actually waiting for the server to started
277 | ;; and using a hack to force wait which was from this link
278 | ;; https://stackoverflow.com/questions/14698081/elisp-sleep-for-doesnt-block-when-running-a-test-in-ert
279 | ;;(sleep-for 10)
280 | (let ((now (float-time))
281 | (process-connection-type nil))
282 | (start-process "tmp" "*tmp*" "bash" "-c" "sleep 1; echo hi")
283 | (while (< (- (float-time) now) 5)
284 | (sleep-for 1))
285 | )
286 | (print (current-time-string))
287 |
288 | (setq create-lockfiles nil)
289 |
290 | (print "buttercup test setup file loaded.")
291 |
292 | ;;; when reading the test output, make it easier to spot when test
293 | ;;; setup noise ends and test results start
294 | (print "\n\n\n\n\n\n\n\n\n\n\n")
295 |
296 |
297 |
298 | ;; todo this needs to be taken into use
299 | ;; (Teardown
300 | ;; ;; After when everything has been run
301 |
302 | ;; (omnisharp--log "TEST: shutting down test server in integration test Teardown hook")
303 | ;; (with-current-buffer "OmniServer"
304 | ;; (let ((filename "omnisharp-server-output.txt"))
305 | ;; (write-file filename)
306 | ;; (print (format "OmniServer buffer contents (available in %s):\n"
307 | ;; filename))
308 | ;; (print (buffer-string))
309 | ;; (kill-process "OmniServer")))
310 |
311 | ;; (with-current-buffer "*omnisharp-debug*"
312 | ;; (let ((filename "omnisharp-debug-output.txt"))
313 | ;; (write-file filename)
314 | ;; (print (format "Debug buffer contents (available in %s):\n"
315 | ;; filename))
316 | ;; (print (buffer-string))))
317 |
318 | ;; (print "Server info:\n")
319 | ;; (print (prin1-to-string omnisharp--server-info))
320 | ;; (print "\n"))
321 |
--------------------------------------------------------------------------------
/omnisharp-utils.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t; -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 | (require 'dash)
17 |
18 | (defun omnisharp--path-to-server (path)
19 | (if (and path (eq system-type 'cygwin))
20 | (cygwin-convert-file-name-to-windows path)
21 | path))
22 |
23 | (defun omnisharp--path-from-server (path)
24 | (if (and path (eq system-type 'cygwin))
25 | (cygwin-convert-file-name-from-windows path)
26 | path))
27 |
28 | (defun omnisharp--get-filename (item)
29 | (omnisharp--path-from-server (cdr (assoc 'FileName item))))
30 |
31 | (defun omnisharp--to-filename (path)
32 | `(FileName . ,(omnisharp--path-to-server path)))
33 |
34 | (defun omnisharp--write-quickfixes-to-compilation-buffer
35 | (quickfixes
36 | buffer-name
37 | buffer-header
38 | &optional dont-save-old-pos)
39 | "Takes a list of QuickFix objects and writes them to the
40 | compilation buffer with HEADER as its header. Shows the buffer
41 | when finished.
42 |
43 | If DONT-SAVE-OLD-POS is specified, will not save current position to
44 | find-tag-marker-ring. This is so this function may be used without
45 | messing with the ring."
46 | (let ((output-in-compilation-mode-format
47 | (mapcar
48 | 'omnisharp--find-usages-output-to-compilation-output
49 | quickfixes)))
50 |
51 | (omnisharp--write-lines-to-compilation-buffer
52 | output-in-compilation-mode-format
53 | (get-buffer-create buffer-name)
54 | buffer-header)
55 | (unless dont-save-old-pos
56 | (ring-insert find-tag-marker-ring (point-marker))
57 | (omnisharp--show-last-buffer-position-saved-message
58 | (buffer-file-name)))))
59 |
60 | (defun omnisharp--write-lines-to-compilation-buffer
61 | (lines-to-write buffer-to-write-to &optional header)
62 | "Writes the given lines to the given buffer, and sets
63 | compilation-mode on. The contents of the buffer are erased. The
64 | buffer is marked read-only after inserting all lines.
65 |
66 | LINES-TO-WRITE are the lines to write, as-is.
67 |
68 | If HEADER is given, that is written to the top of the buffer.
69 |
70 | Expects the lines to be in a format that compilation-mode
71 | recognizes, so that the user may jump to the results."
72 | (with-current-buffer buffer-to-write-to
73 | (let ((inhibit-read-only t))
74 | ;; read-only-mode new in Emacs 24.3
75 | (if (fboundp 'read-only-mode)
76 | (read-only-mode nil)
77 | (setq buffer-read-only nil))
78 | (erase-buffer)
79 |
80 | (when (not (null header))
81 | (insert header))
82 |
83 | (mapc (lambda (element)
84 | (insert element)
85 | (insert "\n"))
86 | lines-to-write)
87 | (compilation-mode)
88 | (if (fboundp 'read-only-mode)
89 | (read-only-mode t)
90 | (setq buffer-read-only t))
91 | (display-buffer buffer-to-write-to))))
92 |
93 | (defun omnisharp--find-usages-output-to-compilation-output
94 | (json-result-single-element)
95 | "Converts a single element of a /findusages JSON response to a
96 | format that the compilation major mode understands and lets the user
97 | follow results to the locations in the actual files."
98 | (let ((filename (omnisharp--get-filename json-result-single-element))
99 | (line (cdr (assoc 'Line json-result-single-element)))
100 | (column (cdr (assoc 'Column json-result-single-element)))
101 | (text (cdr (assoc 'Text json-result-single-element))))
102 | (concat filename
103 | ":"
104 | (prin1-to-string line)
105 | ":"
106 | (prin1-to-string column)
107 | ": \n"
108 | text
109 | "\n")))
110 |
111 | (defun omnisharp--set-buffer-contents-to (filename-for-buffer
112 | new-buffer-contents
113 | &optional
114 | result-point-line
115 | result-point-column)
116 | "Sets the buffer contents to new-buffer-contents for the buffer
117 | visiting filename-for-buffer. If no buffer is visiting that file, does
118 | nothing. Afterwards moves point to the coordinates RESULT-POINT-LINE
119 | and RESULT-POINT-COLUMN.
120 |
121 | If RESULT-POINT-LINE and RESULT-POINT-COLUMN are not given, and a
122 | buffer exists for FILENAME-FOR-BUFFER, its current positions are
123 | used. If a buffer does not exist, the file is visited and the default
124 | point position is used."
125 | (save-window-excursion
126 | (omnisharp--find-file-possibly-in-other-window
127 | filename-for-buffer nil) ; not in other-window
128 |
129 | ;; Default values are the ones in the buffer that is visiting
130 | ;; filename-for-buffer.
131 | (setq result-point-line
132 | (or result-point-line (line-number-at-pos)))
133 | (setq result-point-column
134 | (or result-point-column (omnisharp--current-column)))
135 | (erase-buffer)
136 | (insert new-buffer-contents)
137 |
138 | ;; Hack. Puts point where it belongs.
139 | (omnisharp-go-to-file-line-and-column-worker
140 | result-point-line result-point-column filename-for-buffer)))
141 |
142 | (defun omnisharp--current-column ()
143 | "Returns the current column, converting tab characters in a way that
144 | the OmniSharp server understands."
145 | (let ((tab-width 1))
146 | (1+ (current-column))))
147 |
148 | (defun omnisharp--buffer-exists-for-file-name (file-name)
149 | (let ((all-open-buffers-list (-non-nil (buffer-list))))
150 | (--first (string-equal file-name (buffer-file-name it))
151 | all-open-buffers-list)))
152 |
153 | (defun omnisharp--get-current-buffer-contents ()
154 | (buffer-substring-no-properties (buffer-end 0) (buffer-end 1)))
155 |
156 | (defun omnisharp--log-reset ()
157 | "Kills the *omnisharp-log* buffer"
158 | (let ((log-buffer (get-buffer "*omnisharp-log*")))
159 | (if log-buffer
160 | (kill-buffer log-buffer))))
161 |
162 | (defun omnisharp--log (single-or-multiline-log-string)
163 | "Writes message to the log."
164 | (when omnisharp-debug
165 | (message (concat "*omnisharp-log*: "
166 | (format-time-string "[%H:%M:%S] ")
167 | single-or-multiline-log-string)))
168 |
169 | (let ((log-buffer (get-buffer-create "*omnisharp-log*")))
170 | (save-window-excursion
171 | (with-current-buffer log-buffer
172 | (goto-char (point-max))
173 | (insert (format-time-string "[%H:%M:%S] "))
174 | (insert single-or-multiline-log-string)
175 | (insert "\n")))))
176 |
177 | (defun omnisharp--json-read-from-string (json-string
178 | &optional error-message)
179 | "Deserialize the given JSON-STRING to a lisp object. If
180 | something goes wrong, return a pseudo-packet alist with keys
181 | ServerMessageParseError and Message."
182 | (condition-case possible-error
183 | (json-read-from-string json-string)
184 | (error
185 | (when omnisharp-debug
186 | (omnisharp--log (format "omnisharp--json-read-from-string error: %s reading input %s"
187 | possible-error
188 | json-string)))
189 | (list (cons 'ServerMessageParseError
190 | (or error-message "Error communicating to the OmniSharpServer instance"))
191 | (cons 'Message
192 | json-string)))))
193 |
194 | (defun omnisharp--replace-symbol-in-buffer-with (symbol-to-replace
195 | replacement-string)
196 | "In the current buffer, replaces the given SYMBOL-TO-REPLACE
197 | \(a string\) with REPLACEMENT-STRING."
198 | (search-backward symbol-to-replace)
199 | (replace-match replacement-string t t))
200 |
201 | (defun omnisharp--insert-namespace-import (full-import-text-to-insert)
202 | "Inserts the given text at the top of the current file without
203 | moving point."
204 | (save-excursion
205 | (beginning-of-buffer)
206 | (insert "using " full-import-text-to-insert ";")
207 | (newline)))
208 |
209 | (defun omnisharp--current-word-or-empty-string ()
210 | (or (thing-at-point 'symbol)
211 | ""))
212 |
213 | (defun omnisharp--t-or-json-false (val)
214 | (if val
215 | t
216 | :json-false))
217 |
218 | (defun omnisharp--get-omnisharp-server-executable-command
219 | (solution-file-path &optional server-exe-file-path)
220 | (let* ((server-exe-file-path-arg (expand-file-name
221 | (if (eq nil server-exe-file-path)
222 | omnisharp-server-executable-path
223 | server-exe-file-path)))
224 | (solution-file-path-arg (expand-file-name solution-file-path))
225 | (args (list server-exe-file-path-arg
226 | "-s"
227 | solution-file-path-arg)))
228 | (cond
229 | ((or (equal system-type 'cygwin) ;; No mono needed on cygwin or if using omnisharp-roslyn
230 | (equal system-type 'windows-nt)
231 | (not (s-ends-with? ".exe" server-exe-file-path-arg)))
232 | args)
233 | (t ; some kind of unix: linux or osx
234 | (cons "mono" args)))))
235 |
236 | (defun omnisharp--update-buffer (&optional buffer)
237 | (setq buffer (or buffer (current-buffer)))
238 | (omnisharp--wait-until-request-completed
239 | (omnisharp--send-command-to-server
240 | "updatebuffer"
241 | (omnisharp--get-request-object))))
242 |
243 | (defun omnisharp--update-files-with-text-changes (file-name text-changes)
244 | (let ((file (find-file (omnisharp--convert-backslashes-to-forward-slashes
245 | file-name))))
246 | (with-current-buffer file
247 | (-map 'omnisharp--apply-text-change-to-buffer text-changes))))
248 |
249 | (defun omnisharp--apply-text-change-to-buffer (text-change
250 | &optional buffer)
251 | "Takes a LinePositionSpanTextChange and applies it to the
252 | current buffer.
253 |
254 | If this is used as a response handler, the call to the server
255 | must be blocking (synchronous) so the user doesn't have time to
256 | switch the buffer to some other buffer. That would cause the
257 | changes to be applied to that buffer instead."
258 | (with-current-buffer (or buffer (current-buffer))
259 | (save-excursion
260 | (-let* (((&alist 'NewText new-text
261 | 'StartLine start-line
262 | 'StartColumn start-column
263 | 'EndLine end-line
264 | 'EndColumn end-column) text-change)
265 | ;; In emacs, the first column is 0. On the server, it's
266 | ;; 1. In emacs we always handle the first column as 0.
267 | (start-point (progn
268 | (omnisharp--go-to-line-and-column
269 | start-line
270 | (- start-column 1))
271 | (point)))
272 | (end-point (progn
273 | (omnisharp--go-to-line-and-column
274 | end-line
275 | (- end-column 1))
276 | (point))))
277 |
278 | (delete-region start-point end-point)
279 | (goto-char start-point)
280 | (insert (s-replace (kbd "RET") "" new-text))))))
281 |
282 | (defun omnisharp--handler-exists-for-request (request-id)
283 | (--any? (= request-id (car it))
284 | (cdr (assoc :response-handlers omnisharp--server-info))))
285 |
286 | (defun omnisharp--wait-until-request-completed (request-id
287 | &optional timeout-seconds)
288 | (setq timeout-seconds (or timeout-seconds 30))
289 |
290 | (let ((start-time (current-time))
291 | (process (cdr (assoc :process omnisharp--server-info))))
292 | (while (omnisharp--handler-exists-for-request request-id)
293 | (when (> (cadr (time-subtract (current-time) start-time))
294 | timeout-seconds)
295 | (progn
296 | (let ((msg (format "Request %s did not complete in %s seconds"
297 | request-id timeout-seconds)))
298 | (omnisharp--log msg)
299 | (error msg))))
300 | (accept-process-output process 0.1)))
301 | request-id)
302 |
303 | (defun omnisharp-builtin-completing-read (&rest args)
304 | "Default completing read. See `omnisharp-completing-read-function'"
305 | ;; e.g. ivy and helm don't need a case here, because they set
306 | ;; `completing-read-function' in their mode
307 | (let ((completing-read-variant (cond ((bound-and-true-p ido-mode) 'ido-completing-read)
308 | (t 'completing-read))))
309 | (apply completing-read-variant args)))
310 |
311 | (defun omnisharp--completing-read (&rest args)
312 | "Mockable wrapper for completing-read.
313 | The problem with mocking completing-read directly is that
314 | sometimes the mocks are not removed when an error occurs. This renders
315 | the developer's emacs unusable."
316 | (apply omnisharp-completing-read-function args))
317 |
318 | (defun omnisharp--read-string (&rest args)
319 | "Mockable wrapper for read-string, see
320 | `omnisharp--completing-read' for the explanation."
321 | (apply 'read-string args))
322 |
323 | (defun omnisharp--mkdirp (dir)
324 | "Makes a directory recursively, similarly to a 'mkdir -p'."
325 | (let* ((absolute-dir (expand-file-name dir))
326 | (components (f-split absolute-dir)))
327 | (omnisharp--mkdirp-item (f-join (apply #'concat (-take 1 components))) (-drop 1 components))
328 | absolute-dir))
329 |
330 | (defun omnisharp--mkdirp-item (dir remaining)
331 | "Makes a directory if not exists,
332 | and tries to do the same with the remaining components, recursively."
333 | (unless (f-directory-p dir)
334 | (f-mkdir dir))
335 | (unless (not remaining)
336 | (omnisharp--mkdirp-item (f-join dir (car (-take 1 remaining)))
337 | (-drop 1 remaining))))
338 |
339 | (defun omnisharp--project-root ()
340 | "Tries to resolve project root for current buffer. nil if no project root directory
341 | was found. Uses projectile for the job, falling back to project.el."
342 | ;; use project root as a candidate (if we have projectile available)
343 | (cond ((require 'projectile nil 'noerror)
344 | (condition-case nil
345 | (projectile-project-root)
346 | (error nil)))
347 | ;; otherwise fall back to `project', introduced in Emacs 25.1 core
348 | ((require 'project nil 'noerror)
349 | (condition-case nil
350 | (let ((proj (cdr (project-current))))
351 | (when proj
352 | (expand-file-name proj)))
353 | (error nil)))))
354 |
355 | (defun omnisharp--buffer-contains-metadata()
356 | "Returns t if buffer is omnisharp metadata buffer."
357 | (or (boundp 'omnisharp--metadata-source)
358 | (s-starts-with-p "*omnisharp-metadata:" (buffer-name))))
359 |
360 | (defun omnisharp--message (format-string &rest args)
361 | "Displays passed text using message function."
362 | (apply 'message (cons format-string args)))
363 |
364 | (defun omnisharp--message-at-point (format-string &rest args)
365 | "Displays passed text at point using popup-tip function."
366 | (popup-tip (apply 'format (cons format-string args))))
367 |
368 | (defun omnisharp--truncate-symbol-name (name trunc-length)
369 | "This attempts to truncate a fully-qualified dotnet symbol name to given length.
370 | Basically, in case NAME is longer than TRUNC-LENGTH it will replace text in the middle
371 | with ellipsis (...) so the result would fit into TRUNC-LENGTH.
372 |
373 | It assumes the tail of NAME is more important than the beginning as that usually
374 | has namespaces and parent class name."
375 |
376 | (if (< (length name) trunc-length)
377 | name
378 | (let* ((trunc-length (- trunc-length 3)) ; take ellipsis into account
379 | (trunc-1/4th (/ trunc-length 4))
380 | (head-len (max 0 (- trunc-length (* trunc-1/4th 3))))
381 | (tail-len (max 0 (- trunc-length head-len)))
382 | (head (substring name 0 head-len))
383 | (tail (substring name (- (length name) tail-len))))
384 | (concat head "..." tail))))
385 |
386 | (provide 'omnisharp-utils)
387 |
--------------------------------------------------------------------------------
/omnisharp-server-management.el:
--------------------------------------------------------------------------------
1 | ;; -*- lexical-binding: t; -*-
2 |
3 | ;; This file is free software; you can redistribute it and/or modify
4 | ;; it under the terms of the GNU General Public License as published by
5 | ;; the Free Software Foundation; either version 3, or (at your option)
6 | ;; any later version.
7 |
8 | ;; This file is distributed in the hope that it will be useful,
9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | ;; GNU General Public License for more details.
12 |
13 | ;; You should have received a copy of the GNU General Public License
14 | ;; along with this program. If not, see .
15 |
16 | ;;;
17 | ;; omnisharp--server-info an assoc list is used to track all the metadata
18 | ;; about currently running server.
19 | ;;
20 | ;; NOTE 1: this will go away with multi-server functionality
21 | ;; NOTE 2: you shouldn't use this in user code, this is implementation detail
22 | ;;
23 | ;; keys:
24 | ;; :process - process of the server
25 | ;; :request-id - used for and incremented on every outgoing request
26 | ;; :response-handlers - alist of (request-id . response-handler)
27 | ;; :started? - t if server reported it has started OK and is ready
28 | ;; :project-path - path to server project .sln, .csproj or directory
29 | ;; :project-root - project root directory (based on project-path)
30 | ;; :last-unit-test - a tuple of (test-framework (test-method-names ..))
31 |
32 | (require 'dash)
33 |
34 | (defvar omnisharp--server-info nil)
35 |
36 | (defvar omnisharp--last-project-path nil)
37 | (defvar omnisharp--restart-server-on-stop nil)
38 | (defvar omnisharp-use-http nil "Set to t to use http instead of stdio.")
39 |
40 | (defun make-omnisharp--server-info (process project-path)
41 | (let ((project-root (if (f-dir-p project-path) project-path
42 | (f-dirname project-path))))
43 | ;; see notes on (defvar omnisharp--server-info)
44 | `((:process . ,process)
45 | (:request-id . 1)
46 | (:response-handlers . nil)
47 | (:started? . nil)
48 | (:project-path . ,project-path)
49 | (:project-root . ,project-root)
50 | (:last-unit-test . nil))))
51 |
52 | (defun omnisharp--resolve-omnisharp-server-executable-path ()
53 | "Attempts to resolve a path to local executable for the omnisharp-roslyn server.
54 | Will return `omnisharp-server-executable-path` override if set, otherwise will attempt
55 | to use server installed via `omnisharp-install-server`.
56 |
57 | Failing all that an error message will be shown and nil returned."
58 | (if omnisharp-server-executable-path
59 | omnisharp-server-executable-path
60 | (let ((server-installation-path (omnisharp--server-installation-path)))
61 | (if server-installation-path
62 | server-installation-path
63 | (progn
64 | (omnisharp--message "omnisharp: No omnisharp server could be found.")
65 | (omnisharp--message (concat "omnisharp: Please use M-x 'omnisharp-install-server' or download server manually"
66 | " as detailed in https://github.com/OmniSharp/omnisharp-emacs/blob/master/doc/server-installation.md"))
67 | nil)))))
68 |
69 | (defun omnisharp--do-server-start (project-root)
70 | (let ((server-executable-path (omnisharp--resolve-omnisharp-server-executable-path)))
71 | (message (format "omnisharp: starting server on project root: \"%s\"" project-root))
72 |
73 | (omnisharp--log-reset)
74 | (omnisharp--log (format "starting server on project root \"%s\"" project-root))
75 | (omnisharp--log (format "Using server binary on %s" server-executable-path))
76 |
77 | ;; Save all csharp buffers to ensure the server is in sync"
78 | (save-some-buffers t (lambda () (and (buffer-file-name) (string-equal (file-name-extension (buffer-file-name)) "cs"))))
79 |
80 | (setq omnisharp--last-project-path project-root)
81 |
82 | ;; this can be set by omnisharp-reload-solution to t
83 | (setq omnisharp--restart-server-on-stop nil)
84 |
85 | (setq omnisharp--server-info
86 | (make-omnisharp--server-info
87 | ;; use a pipe for the connection instead of a pty
88 | (let* ((process-connection-type nil)
89 | (default-directory (expand-file-name project-root))
90 | (omnisharp-process (start-process
91 | "OmniServer" ; process name
92 | "OmniServer" ; buffer name
93 | server-executable-path
94 | "--encoding" "utf-8"
95 | "--stdio")))
96 | (buffer-disable-undo (process-buffer omnisharp-process))
97 | (set-process-query-on-exit-flag omnisharp-process nil)
98 | (set-process-filter omnisharp-process 'omnisharp--handle-server-message)
99 | (set-process-sentinel omnisharp-process
100 | (lambda (process event)
101 | (when (memq (process-status process) '(exit signal))
102 | (message "omnisharp: server has been terminated")
103 | (setq omnisharp--server-info nil)
104 | (if omnisharp--restart-server-on-stop
105 | (omnisharp--do-server-start omnisharp--last-project-path)))))
106 | omnisharp-process)
107 | project-root))))
108 |
109 | (defun omnisharp--clear-response-handlers ()
110 | "For development time cleaning up impossible states of response
111 | handlers in the current omnisharp--server-info."
112 | (setcdr (assoc :response-handlers omnisharp--server-info)
113 | nil))
114 |
115 | (defmacro comment (&rest body) nil)
116 | (comment (omnisharp--clear-response-handlers))
117 |
118 | (defun omnisharp--send-command-to-server (api-name contents &optional response-handler async)
119 | "Sends the given command to the server.
120 | Depending on omnisharp-use-http it will either send it via http or stdio.
121 | The variable ASYNC has no effect when not using http."
122 |
123 | (if omnisharp-use-http
124 | (omnisharp--send-command-to-server-http api-name contents response-handler async)
125 | (omnisharp--send-command-to-server-stdio api-name contents response-handler)))
126 |
127 | (defun omnisharp--send-command-to-server-http (api-name contents response-handler &optional async)
128 | "Sends the given command via curl"
129 | (omnisharp-post-http-message api-name response-handler contents async))
130 |
131 | (defun omnisharp--send-command-to-server-stdio (api-name contents &optional response-handler)
132 | "Sends the given command to the server and associates a
133 | response-handler for it. The server will respond to this request
134 | later and the response handler will get called then.
135 |
136 | Returns the unique request id that the request is given before
137 | sending."
138 | ;; make RequestPacket with request-id
139 | ;; send request
140 | ;; store response handler associated with the request id
141 | (if (equal nil omnisharp--server-info)
142 | (message (concat "omnisharp: server is not running. "
143 | "Start it with `omnisharp-start-omnisharp-server' first"))
144 | (if (not (s-starts-with? "/" api-name))
145 | (setq api-name (concat "/" api-name)))
146 |
147 | (-let* ((server-info omnisharp--server-info)
148 | ((&alist :process process
149 | :request-id request-id) server-info)
150 | (request (omnisharp--make-request-packet api-name
151 | contents
152 | request-id)))
153 | (when omnisharp-debug
154 | (omnisharp--log (format "--> %s %s %s"
155 | request-id
156 | api-name
157 | (prin1-to-string request))))
158 |
159 | ;; update current request-id and associate a response-handler for
160 | ;; this request
161 | (setcdr (assoc :request-id server-info) (+ 1 request-id))
162 |
163 | ;; requests that don't require handling are still added with a
164 | ;; dummy handler. This means they are pending. This is required
165 | ;; so that omnisharp--wait-until-request-completed can know when
166 | ;; the requests have completed.
167 | (setcdr (assoc :response-handlers server-info)
168 | (-concat `((,request-id . ,(or response-handler #'identity)))
169 | (cdr (assoc :response-handlers server-info))))
170 |
171 | (process-send-string process (concat (json-encode request) "\n"))
172 | request-id)))
173 |
174 | (defun omnisharp--send-command-to-server-sync (&rest args)
175 | "Like `omnisharp--send-command-to-server' but will block until the
176 | request responded by the server."
177 | (omnisharp--wait-until-request-completed
178 | (apply 'omnisharp--send-command-to-server args)))
179 |
180 | (defun omnisharp--make-request-packet (api-name contents request-id)
181 | (-concat `((Arguments . ,contents))
182 | `((Command . ,api-name)
183 | (Seq . ,request-id))))
184 |
185 | (defun omnisharp--handle-server-message (process message-part)
186 | "Parse alists from accumulated json responses in the server's
187 | process buffer, and handle them as server events"
188 | (condition-case maybe-error-data
189 | (let* ((messages-from-server (omnisharp--read-lines-from-process-output
190 | process message-part))
191 | (error-message (concat
192 | "The server sent an unknown json message. "
193 | "Inspect the omnisharp-server process buffer "
194 | "to view recent messages from the server. "
195 | "Set `omnisharp-debug' to t and inspect the "
196 | "*omnisharp-debug* buffer for this error specifically."))
197 | (json-messages (-map (lambda (json-string)
198 | (omnisharp--json-read-from-string json-string error-message))
199 | messages-from-server)))
200 | ;; should use -each here since it's for side effects only, but
201 | ;; it can't work with vectors. -map can, so use that instead.
202 | (-map #'omnisharp--handle-server-event json-messages))
203 | (error (let ((msg (format (concat "omnisharp--handle-server-message error: %s. "
204 | "See the OmniServer process buffer for detailed server output.")
205 | (prin1-to-string maybe-error-data))))
206 | (omnisharp--log msg)
207 | (message msg)))))
208 |
209 | (defun omnisharp--log-packet? (packet)
210 | (and (equal "event" (cdr (assoc 'Type packet)))
211 | (equal "log" (cdr (assoc 'Event packet)))))
212 |
213 | (defun omnisharp--log-log-packet (packet)
214 | (-let (((&alist 'LogLevel log-level
215 | 'Name name
216 | 'Message message) (cdr (assoc 'Body packet))))
217 | (omnisharp--log (format "%s: %s, %s" log-level name message))
218 | (if (string-equal name "OmniSharp.Startup")
219 | (message (format "omnisharp: %s, %s" name message)))))
220 |
221 | (defun omnisharp--event-packet? (packet)
222 | (and (equal "event" (cdr (assoc 'Type packet)))))
223 |
224 | (defun omnisharp--response-packet? (packet)
225 | (equal "response" (cdr (assoc 'Type packet))))
226 |
227 | (defun omnisharp--ignorable-packet? (packet)
228 | ;; todo what exactly are these? can they be ignored?
229 | (and (assq 'Arguments packet)
230 | (assq 'Command packet)))
231 |
232 | (defun omnisharp--handle-event-packet (packet server-info)
233 | (-let (((&alist 'Type packet-type 'Event event-type) packet))
234 | (cond ((-contains? '("ProjectAdded" "ProjectChanged") event-type)
235 | (comment ignore these for now.))
236 | ((equal "TestMessage" event-type)
237 | (apply 'omnisharp--handle-test-message-event (list packet)))
238 | ((equal "started" event-type)
239 | (omnisharp--message "omnisharp: server has been started, check *omnisharp-log* for startup progress messages")
240 | (setcdr (assoc :started? server-info) t)))))
241 |
242 | (defun omnisharp--handle-server-event (packet)
243 | "Takes an alist representing some kind of Packet, possibly a
244 | ResponsePacket or an EventPacket, and processes it depending on
245 | its type."
246 | (let ((server-info omnisharp--server-info))
247 | (cond ((omnisharp--ignorable-packet? packet)
248 | nil)
249 |
250 | ((omnisharp--response-packet? packet)
251 | (omnisharp--handle-server-response-packet packet server-info))
252 |
253 | ((omnisharp--log-packet? packet)
254 | (omnisharp--log-log-packet packet))
255 |
256 | ((omnisharp--event-packet? packet)
257 | (omnisharp--handle-event-packet packet server-info))
258 |
259 | (t (progn
260 | (omnisharp--log (format "<-- Received an unknown server packet: %s"
261 | (prin1-to-string packet))))))))
262 |
263 | (defun omnisharp--remove-response-handler (server-info request-id)
264 | (setcdr (assoc :response-handlers server-info)
265 | (--remove (= (car it) request-id)
266 | (-non-nil (cdr (assoc :response-handlers server-info))))))
267 |
268 | (defun omnisharp--handle-server-response-packet (packet server-info)
269 | "Calls the appropriate response callback for the received packet"
270 | (-let (((&alist 'Message message
271 | 'Body body
272 | 'Command command
273 | 'Success success?
274 | 'Request_seq request-id) packet)
275 | ((&alist :response-handlers response-handlers) server-info))
276 | ;; try to find the matching response-handler
277 | (-if-let* ((id-and-handler (--first (= (car it) request-id)
278 | response-handlers)))
279 | (-let (((request-id . response-handler) id-and-handler))
280 | (condition-case maybe-error-data
281 | (progn
282 | (if (equal success? :json-false)
283 | (omnisharp--log (format "<-- %s %s: request failed"
284 | request-id
285 | command
286 | (prin1-to-string body))))
287 | (omnisharp--remove-response-handler server-info request-id)
288 | (when (equal t success?)
289 | (apply response-handler (list body))))
290 | (error
291 | (progn
292 | (let ((msg (format
293 | (concat "\n"
294 | "omnisharp--handle-server-response-packet error: \n%s.\n\n"
295 | "Tried to handle this packet: \n%s\n\n"
296 | "This can mean an error in the handler function:\n%s\n\n")
297 | (prin1-to-string maybe-error-data)
298 | (prin1-to-string packet)
299 | (prin1-to-string response-handler))))
300 | (omnisharp--log msg)
301 | (omnisharp--remove-response-handler server-info request-id)
302 | (message msg))))))
303 |
304 | (omnisharp--log (format "<-- %s %s: Warning: internal error - response has no handler: %s"
305 | request-id
306 | command
307 | body)))))
308 |
309 | (defun omnisharp--at-full-line? ()
310 | ;; all platforms use \n as newline in emacs
311 | (s-ends-with? "\n"
312 | (substring-no-properties (or (thing-at-point 'line)
313 | ""))))
314 |
315 | (defun omnisharp--marker-at-full-line? (position-or-marker)
316 | (save-excursion
317 | (goto-char position-or-marker)
318 | (omnisharp--at-full-line?)))
319 |
320 | (defun omnisharp--read-lines-from-process-output (process message-part)
321 | "Problem: emacs reads output from the omnisharp-roslyn subprocess
322 | not line by line, but by some amount of characters. The way we want
323 | to read the omnisharp-roslyn output is line by line, since each
324 | response seems to be exactly one line long.
325 |
326 | This function returns full lines returned from the server process that
327 | have not been returned before."
328 | (when (buffer-live-p (process-buffer process))
329 | (with-current-buffer (process-buffer process)
330 | ;; previous-text-marker will change if it refers to the marker
331 | ;; and the marker is changed. Get it as an integer instead to
332 | ;; avoid mutation
333 | (let ((previous-text-marker (save-excursion
334 | (goto-char (process-mark process))
335 | (point))))
336 | ;; Insert the text, advancing the process marker.
337 | (goto-char (buffer-end 1))
338 | (insert message-part)
339 | ;; Return previous pending lines only when the latest line
340 | ;; is complete. Might be slightly slower but easier to
341 | ;; implement
342 | (when (omnisharp--marker-at-full-line? (point))
343 | (set-marker (process-mark process) (point))
344 | ;; get the start of the last inserted line
345 | (goto-char previous-text-marker)
346 | (beginning-of-line)
347 | (let ((text (s-lines (buffer-substring-no-properties
348 | (point)
349 | (process-mark process))))
350 | (trim-bom (lambda (s) (string-remove-prefix "\ufeff" (string-remove-prefix "\ufeff" s)))))
351 | ;; don't store messages in the process buffer unless
352 | ;; debugging, as they can slow emacs down when they pile
353 | ;; up
354 | (when (not omnisharp-debug) (erase-buffer))
355 | (-map trim-bom (--filter (not (s-blank? it)) text))))))))
356 |
357 | (defun omnisharp--attempt-to-start-server-for-buffer ()
358 | "Checks if the server for the project of the buffer is running
359 | and attempts to start it if it is not."
360 |
361 | (unless (or (omnisharp--buffer-contains-metadata)
362 | (not (buffer-file-name)))
363 | (let* ((project-root (omnisharp--project-root))
364 | (server-project-root (if omnisharp--server-info (cdr (assoc :project-root omnisharp--server-info)) nil))
365 | (filename (buffer-file-name))
366 | (filename-in-scope (and server-project-root
367 | (f-same-p (f-common-parent (list filename server-project-root))
368 | server-project-root))))
369 | (cond ((and (not server-project-root) project-root)
370 | (omnisharp--do-server-start project-root))
371 |
372 | ((and (not server-project-root) (not project-root))
373 | (message (concat "omnisharp: no project root could be found to start omnisharp server for this buffer automatically"))
374 | (message "omnisharp: start the server manually with M-x omnisharp-start-omnisharp-server or make sure project root is discoverable by projectile"))
375 |
376 | ((and server-project-root (not filename-in-scope))
377 | (message (format (concat "omnisharp: buffer will not be managed by omnisharp: "
378 | "%s is outside the root directory of the project loaded on the "
379 | "current OmniSharp server: %s")
380 | filename
381 | server-project-root)))))))
382 |
383 | (provide 'omnisharp-server-management)
384 |
--------------------------------------------------------------------------------