├── start-emacs-in-this-project.sh ├── test ├── MinimalProject │ ├── .gitignore │ ├── FlycheckTest.cs │ ├── README.md │ ├── MyClass.cs │ ├── MyClassContainer.cs │ ├── MinimalProject.csproj │ └── NuGet.config ├── buttercup-tests │ ├── readme.md │ ├── current-symbol │ │ ├── current-type-information-test.el │ │ ├── find-usages-test.el │ │ ├── find-usages-with-ido-test.el │ │ ├── navigate-to-current-file-member-test.el │ │ ├── find-implementations-test.el │ │ ├── find-implementations-with-ido-test.el │ │ └── rename-symbol-test.el │ ├── utils │ │ └── update-buffer-test.el │ ├── formatting │ │ ├── code-format-test.el │ │ └── formatting-test.el │ ├── navigation │ │ ├── imenu-test.el │ │ ├── navigate-to-solution-file-test.el │ │ ├── navigate-to-region-test.el │ │ ├── find-symbols-test.el │ │ └── go-to-definition-test.el │ ├── solution │ │ ├── solution-errors-test.el │ │ └── code-actions │ │ │ └── fix-code-issue-test.el │ ├── flycheck │ │ └── flycheck-test.el │ ├── auto-complete │ │ ├── auto-complete-company-test.el │ │ └── auto-complete-popup-test.el │ └── setup.el ├── integration-test.el ├── find-usages-test.el ├── server-actions-test.el ├── test-helper.el ├── server-management-test.el └── unit-test.el ├── doc ├── pics │ ├── eldoc.png │ ├── tests.png │ ├── rename.png │ ├── helm-imenu.png │ ├── override-result.png │ ├── auto-complete-ido.png │ ├── company-mode-popup.png │ ├── auto-complete-popup.png │ ├── override-suggestions.png │ ├── company-mode-doc-buffer.png │ ├── company-mode-parameters.png │ ├── refactoring-suggestions.png │ ├── syntax-error-flycheck.png │ ├── company-mode-popup-complex.png │ ├── omnisharp-helm-find-symbols.png │ ├── omnisharp-helm-find-usages.png │ ├── auto-complete-popup-documentation.png │ ├── navigate-to-current-type-member.png │ ├── navigate-to-type-in-current-file.png │ └── build-solution-in-compilation-buffer.png ├── example-config-for-evil-mode.el ├── server-installation.md └── features.md ├── test-stuff ├── run-tests.sh ├── melpa-testing.recipe ├── run-all-tests.sh ├── run-integration-tests.sh └── run-melpa-build-test.sh ├── ignored-from-melpa-build ├── readme.md ├── melpa-build-test.el ├── load-omnisharp-development-files.el └── prodigy-config.el ├── .gitignore ├── run-tests.sh ├── Cask ├── docker-compose.yml ├── features-tbd-on-the-server-side ├── complete-overrides.feature └── fix-usings.feature ├── .github └── workflows │ └── run-tests.yaml ├── Dockerfile.testing ├── omnisharp-http-utils.el ├── omnisharp-server-actions.el ├── omnisharp-helm-integration.el ├── omnisharp-format-actions.el ├── omnisharp-code-structure.el ├── omnisharp-server-installation.el ├── omnisharp-solution-actions.el ├── omnisharp-unit-test-actions.el ├── omnisharp-current-symbol-actions.el ├── omnisharp-settings.el ├── omnisharp-navigation-actions.el ├── README.md ├── omnisharp-utils.el └── omnisharp-server-management.el /start-emacs-in-this-project.sh: -------------------------------------------------------------------------------- 1 | cask exec emacs & 2 | -------------------------------------------------------------------------------- /test/MinimalProject/.gitignore: -------------------------------------------------------------------------------- 1 | project.lock.json 2 | RenameFileTest.cs 3 | -------------------------------------------------------------------------------- /doc/pics/eldoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/eldoc.png -------------------------------------------------------------------------------- /doc/pics/tests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/tests.png -------------------------------------------------------------------------------- /test/MinimalProject/FlycheckTest.cs: -------------------------------------------------------------------------------- 1 | public class MyClass { 2 | DoesNotExist foo; 3 | } 4 | -------------------------------------------------------------------------------- /test/MinimalProject/README.md: -------------------------------------------------------------------------------- 1 | This is a sample solution that is used for integration tests. 2 | -------------------------------------------------------------------------------- /doc/pics/rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/rename.png -------------------------------------------------------------------------------- /doc/pics/helm-imenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/helm-imenu.png -------------------------------------------------------------------------------- /doc/pics/override-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/override-result.png -------------------------------------------------------------------------------- /test/MinimalProject/MyClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace minimal 3 | { 4 | public class MyClass {} 5 | } 6 | -------------------------------------------------------------------------------- /doc/pics/auto-complete-ido.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/auto-complete-ido.png -------------------------------------------------------------------------------- /doc/pics/company-mode-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/company-mode-popup.png -------------------------------------------------------------------------------- /doc/pics/auto-complete-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/auto-complete-popup.png -------------------------------------------------------------------------------- /doc/pics/override-suggestions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/override-suggestions.png -------------------------------------------------------------------------------- /doc/pics/company-mode-doc-buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/company-mode-doc-buffer.png -------------------------------------------------------------------------------- /doc/pics/company-mode-parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/company-mode-parameters.png -------------------------------------------------------------------------------- /doc/pics/refactoring-suggestions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/refactoring-suggestions.png -------------------------------------------------------------------------------- /doc/pics/syntax-error-flycheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/syntax-error-flycheck.png -------------------------------------------------------------------------------- /doc/pics/company-mode-popup-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/company-mode-popup-complex.png -------------------------------------------------------------------------------- /doc/pics/omnisharp-helm-find-symbols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/omnisharp-helm-find-symbols.png -------------------------------------------------------------------------------- /doc/pics/omnisharp-helm-find-usages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/omnisharp-helm-find-usages.png -------------------------------------------------------------------------------- /doc/pics/auto-complete-popup-documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/auto-complete-popup-documentation.png -------------------------------------------------------------------------------- /doc/pics/navigate-to-current-type-member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/navigate-to-current-type-member.png -------------------------------------------------------------------------------- /doc/pics/navigate-to-type-in-current-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/navigate-to-type-in-current-file.png -------------------------------------------------------------------------------- /doc/pics/build-solution-in-compilation-buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmniSharp/omnisharp-emacs/HEAD/doc/pics/build-solution-in-compilation-buffer.png -------------------------------------------------------------------------------- /test-stuff/run-tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Runs all tests for the project 3 | 4 | # Remember to run cask install if the dependencies have changed 5 | cask exec ert-runner 6 | -------------------------------------------------------------------------------- /test/MinimalProject/MyClassContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace minimal 3 | { 4 | public class MyClassContainer 5 | { 6 | public MyClass foo; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/MinimalProject/MinimalProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /ignored-from-melpa-build/readme.md: -------------------------------------------------------------------------------- 1 | Here lay stuff that does not get bundled to the installation package 2 | by melpa when a new commit is pushed to github. See the melpa recipe 3 | for omnisharp for the exact details. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.cask/ 2 | /melpa/ 3 | /installation-output.txt 4 | /node_modules/ 5 | # backup files by emacs 6 | /test-cache/ 7 | /test/MinimalProject/obj/* 8 | /test/MinimalProject/KeystrokeTest.cs 9 | *~ 10 | *.elc 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /test-stuff/melpa-testing.recipe: -------------------------------------------------------------------------------- 1 | (omnisharp :repo "OmniSharp/omnisharp-emacs" 2 | :fetcher github 3 | :branch "master" 4 | :files ("*.el" 5 | "src/*.el" 6 | "src/actions/*.el")) 7 | -------------------------------------------------------------------------------- /test/buttercup-tests/readme.md: -------------------------------------------------------------------------------- 1 | Each folder contains tests that are defined in an actions file. For 2 | example, [auto-complete tests][] are tests for functions in 3 | [auto-complete-actions][]. 4 | 5 | [auto-complete tests]: ./auto-complete/ 6 | [auto-complete-actions]: ../../omnisharp-auto-complete-actions.el 7 | 8 | -------------------------------------------------------------------------------- /test/MinimalProject/NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test-stuff/run-all-tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | set -o pipefail 3 | 4 | assertTestPasses() { 5 | echo $1 6 | eval $1 7 | } 8 | 9 | # https://github.com/cask/cask/issues/241 10 | find .cask -name "*.elc" -delete 11 | 12 | assertTestPasses "./test-stuff/run-tests.sh" 13 | assertTestPasses "./test-stuff/run-integration-tests.sh" 14 | assertTestPasses "./test-stuff/run-melpa-build-test.sh" 15 | -------------------------------------------------------------------------------- /test/integration-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | ;; 3 | ;; You can run tests with M-x ert but remember to evaluate them before 4 | ;; running if you changed something! 5 | 6 | ;; For these tests, an OmniSharpServer process needs to be running. 7 | 8 | (require 'ert-async) 9 | 10 | (ert-deftest server-running-stdio-doesnt-crash-test () 11 | (omnisharp-check-ready-status)) 12 | -------------------------------------------------------------------------------- /test/find-usages-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (ert-deftest omnisharp--find-usages-show-response-doesnt-show-zero-quickfixes () 4 | (with-mock 5 | (mock (omnisharp--message-at-point "No usages found.") => "") 6 | (stub omnisharp--write-quickfixes-to-compilation-buffer => 7 | (error "should not show usages when there are none")) 8 | (omnisharp--find-usages-show-response nil))) 9 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | set -o pipefail 3 | 4 | check_command_exists () { 5 | type "$1" &> /dev/null 6 | } 7 | 8 | if ! check_command_exists docker; then 9 | echo "You have to install docker command" 10 | exit 1 11 | fi 12 | 13 | if ! check_command_exists docker-compose; then 14 | echo "You have to install docker-compose command" 15 | exit 1 16 | fi 17 | 18 | mkdir -p test-cache/emacs-d-cache 19 | 20 | docker-compose build 21 | docker-compose run --rm regular-tests && echo "Tests ran OK" || echo "Tests FAILED!" 22 | -------------------------------------------------------------------------------- /test-stuff/run-integration-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "'dotnet --version' reports:" $(dotnet --version) 4 | 5 | if [[ $(dotnet --version) != "2.1"* ]]; then 6 | echo "Must install the .NET CLI 2.1.* http://dotnet.github.io/" 7 | exit 1 8 | fi 9 | 10 | echo "Building MinimalProject" 11 | pushd test/MinimalProject 12 | dotnet build 13 | popd 14 | 15 | TERM=dumb SHELL=sh cask exec emacs \ 16 | -Q \ 17 | -batch \ 18 | -f package-initialize \ 19 | -l buttercup \ 20 | -l "test/buttercup-tests/setup.el" \ 21 | -f buttercup-run-discover "test/buttercup-tests/" 22 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source melpa) 2 | (source gnu) 3 | 4 | (package-file "omnisharp.el") 5 | 6 | (development 7 | (depends-on "cl") 8 | (depends-on "cl-lib") 9 | (depends-on "helm") 10 | (depends-on "yasnippet") 11 | (depends-on "prodigy") 12 | (depends-on "buttercup") 13 | (depends-on "ert-async") 14 | (depends-on "noflet") 15 | (depends-on "s") 16 | (depends-on "ert-runner") 17 | (depends-on "el-mock") 18 | (depends-on "json") 19 | (depends-on "dash") 20 | (depends-on "popup") 21 | (depends-on "espuds") 22 | (depends-on "auto-complete") 23 | (depends-on "flycheck") 24 | (depends-on "csharp-mode") 25 | (depends-on "evil") 26 | (depends-on "f") 27 | (depends-on "company")) 28 | -------------------------------------------------------------------------------- /test/buttercup-tests/current-symbol/current-type-information-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Current type information" 4 | (it "lists usages of the symbol under point" 5 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 6 | (spy-on 'omnisharp--message-at-point nil) 7 | (ot--buffer-contents-and-point-at-$ 8 | "namespace minimal" 9 | "{" 10 | " public class Tar$get {}" 11 | "}") 12 | 13 | (omnisharp--wait-until-request-completed (omnisharp-current-type-information)) 14 | (expect 'omnisharp--message-at-point :to-have-been-called-with "minimal.Target"))) 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | regular-tests: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile.testing 7 | image: omnisharp-testing 8 | volumes: 9 | - '.:/tmp/src' 10 | command: bash -c "cp /tmp/src/*.el /usr/src/ && cp /tmp/src/test/*.el /usr/src/test/ && cp -r /tmp/src/test/buttercup-tests /usr/src/test/ && ./test-stuff/run-tests.sh && ./test-stuff/run-integration-tests.sh" 11 | melpa-tests: 12 | build: 13 | context: . 14 | dockerfile: Dockerfile.testing 15 | image: omnisharp-testing 16 | volumes: 17 | - '.:/tmp/src' 18 | command: bash -c "cp /tmp/src/*.el /usr/src/ && cp /tmp/src/test/*.el /usr/src/test/ && ./test-stuff/run-melpa-build-test.sh" 19 | -------------------------------------------------------------------------------- /test/buttercup-tests/utils/update-buffer-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Update buffer" 4 | (before-each 5 | (ot--open-the-minimal-project-source-file "MyClass.cs") 6 | (ot--buffer-contents-and-point-at-$ 7 | "//contents don't matter$")) 8 | 9 | (it "does not crash when called" 10 | ;; no easy way to verify the server has done its thing 11 | (omnisharp--update-buffer)) 12 | 13 | ;; This is actually a test for omnisharp--remove-response-handler, 14 | ;; sort of. But sue me! :D 15 | (it "cleans up its response handler after it's done" 16 | (let ((request-id (omnisharp--update-buffer))) 17 | (expect (not (omnisharp--handler-exists-for-request request-id)))))) 18 | -------------------------------------------------------------------------------- /test/buttercup-tests/current-symbol/find-usages-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Find usages" 4 | (before-each 5 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs")) 6 | 7 | (it "lists usages of the symbol under point" 8 | (ot--buffer-contents-and-point-at-$ 9 | "using System;" 10 | "namespace minimal" 11 | "{" 12 | " public class Target {}" 13 | " public class JumpSite {" 14 | " Targ$et foo;" 15 | " }" 16 | "}") 17 | 18 | (ot--evaluate-and-wait-for-server-response "(omnisharp-find-usages)") 19 | (ot--switch-to-buffer "* OmniSharp : Usages *") 20 | (ot--i-should-see "Usages in the current solution:") 21 | (ot--i-should-see "public class Target {}"))) 22 | -------------------------------------------------------------------------------- /test/buttercup-tests/current-symbol/find-usages-with-ido-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Find usages with ido" 4 | (before-each 5 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs")) 6 | 7 | (it "lists usages of the symbol under point" 8 | (ot--buffer-contents-and-point-at-$ 9 | "using System;" 10 | "namespace minimal" 11 | "{" 12 | " public class Target {}" 13 | " public class JumpSite {" 14 | " Targ$et foo;" 15 | " }" 16 | "}") 17 | 18 | (ot--answer-omnisharp--completing-read-with #'-first-item) 19 | 20 | (omnisharp--wait-until-request-completed (omnisharp-find-usages-with-ido)) 21 | 22 | (ot--point-should-be-on-a-line-containing "public class Target {}"))) 23 | -------------------------------------------------------------------------------- /ignored-from-melpa-build/melpa-build-test.el: -------------------------------------------------------------------------------- 1 | (require 'package) 2 | (require 'files) 3 | 4 | (let ((travis-branch (getenv "TRAVIS_BRANCH"))) 5 | (print "Current branch") 6 | (print (shell-command-to-string "git rev-parse --abbrev-ref HEAD"))) 7 | 8 | ;; should be run in the repo root directory 9 | 10 | (setq package-archives 11 | '(("melpa" . "http://melpa.org/packages/") 12 | ("gnu elpa" . "http://elpa.gnu.org/packages/"))) 13 | 14 | (package-initialize) 15 | (package-refresh-contents) 16 | 17 | (let ((file (expand-file-name 18 | (car (file-expand-wildcards "melpa/packages/omnisharp-*.tar"))))) 19 | (message "installing file %s" file) 20 | (package-install-file file) 21 | 22 | (require 'omnisharp) 23 | (if (featurep 'omnisharp) 24 | (print "Installation successful lololololol") 25 | (print "Installation failed"))) 26 | -------------------------------------------------------------------------------- /features-tbd-on-the-server-side/complete-overrides.feature: -------------------------------------------------------------------------------- 1 | Feature: Complete overrides 2 | 3 | Background: 4 | Given I open the MinimalSolution source file "minimal/MyClassContainer.cs" 5 | Given The buffer is empty 6 | 7 | Scenario: Complete overrides in the current buffer with ido 8 | When My buffer contents are, and my point is at $: 9 | """ 10 | namespace Test { 11 | public class Awesome { 12 | $ 13 | } 14 | } 15 | """ 16 | Given I start an action chain 17 | And I press "M-x" 18 | And I type "omnisharp-auto-complete-overrides" 19 | # Finish M-x 20 | And I press "RET" 21 | # Select the first proposed override 22 | And I press "RET" 23 | And I execute the action chain 24 | 25 | Then I should see, ignoring line endings: 26 | """ 27 | public override bool Equals(object obj) 28 | """ 29 | -------------------------------------------------------------------------------- /test/buttercup-tests/formatting/code-format-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Code format" 4 | (before-each 5 | (ot--open-the-minimal-project-source-file "CodeFormatTest.cs")) 6 | 7 | (it "can format the entire buffer contents" 8 | (ot--buffer-contents-and-point-at-$ 9 | "public class CodeFormatTest { $ }") 10 | (omnisharp-code-format-entire-file) 11 | (ot--buffer-should-contain "public class CodeFormatTest { }")) 12 | 13 | (it "can format the current region" 14 | (ot--buffer-contents-and-region 15 | "public class Foo{" 16 | " public int Lol (region-starts-here){get; $ set;}(region-ends-here)" 17 | "}") 18 | (omnisharp-code-format-region) 19 | (ot--buffer-should-contain 20 | "public class Foo{" 21 | " public int Lol { get; set; }" 22 | "}"))) 23 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | run-melpa-tests: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | 17 | - name: docker-compose build 18 | run: docker-compose build 19 | 20 | - name: docker-compose run --rm melpa-tests 21 | run: docker-compose run --rm melpa-tests 22 | 23 | run-regular-tests: 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v1 28 | 29 | - name: omnisharp server cache dir 30 | uses: actions/cache@v2 31 | with: 32 | path: ~/.emacs.d/.cache/omnisharp/server/ 33 | key: ${{ runner.os }}-emacs-d-cache-omnisharp-server 34 | 35 | - name: docker-compose build 36 | run: docker-compose build 37 | 38 | - name: docker-compose run --rm regular-tests 39 | run: docker-compose run --rm regular-tests 40 | -------------------------------------------------------------------------------- /test/server-actions-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (ert-deftest check-ready-status-test () 4 | (with-server-returning "checkreadystatus" t 5 | (should (equal "Server is ready" 6 | (omnisharp-check-ready-status)))) 7 | 8 | (with-server-returning "checkreadystatus" nil 9 | (should (equal "Server is not ready yet" 10 | (omnisharp-check-ready-status))))) 11 | 12 | (ert-deftest check-available-test () 13 | (with-server-returning "checkalivestatus" t 14 | (should (equal "Server is alive and well. Happy coding!" 15 | (omnisharp-check-alive-status)))) 16 | 17 | (with-server-returning "checkalivestatus" nil 18 | (should (equal "Server is not alive" 19 | (omnisharp-check-alive-status))))) 20 | -------------------------------------------------------------------------------- /test/buttercup-tests/current-symbol/navigate-to-current-file-member-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Navigate to current file member" 4 | (it "moves point to selected type" 5 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 6 | (ot--buffer-contents-and-point-at-$ 7 | "using System;" 8 | "namespace minimal" 9 | "{" 10 | " public class Class_One {}" 11 | " // point location does not matter" 12 | " public class $Class_Two {" 13 | " public int Foo = 0;" 14 | " }" 15 | " public class Class_Three {}" 16 | "}") 17 | 18 | ;; automatically select the first candidate given to 19 | ;; omnisharp--choose-quickfix-ido. 20 | (ot--answer-omnisharp--completing-read-with #'-first-item) 21 | 22 | (omnisharp--wait-until-request-completed 23 | (omnisharp-navigate-to-current-file-member)) 24 | 25 | (ot--point-should-be-on-a-line-containing "public class Class_One {}"))) 26 | -------------------------------------------------------------------------------- /Dockerfile.testing: -------------------------------------------------------------------------------- 1 | FROM silex/emacs:27-ci-cask 2 | 3 | WORKDIR /usr/src 4 | 5 | # Install dependencies 6 | RUN apt-get update \ 7 | && apt-get install -y libicu63 \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -v 2.1.816 11 | ENV PATH /root/.dotnet:$PATH 12 | RUN dotnet --version 13 | 14 | # Install omnisharp and it's dependency packages via Cask 15 | COPY Cask Cask 16 | COPY omnisharp*.el ./ 17 | RUN cask install || true 18 | 19 | # run cask build to check if the thing compiles to byte code 20 | RUN ! (cask build 2>&1 | tee /dev/stderr | grep -iq "^.*\\.el:.*:Error:") 21 | 22 | # Copy other files 23 | # COPY doc doc 24 | # COPY features-tbd-on-the-server-side features-tbd-on-the-server-side 25 | # COPY ignored-from-melpa-build ignored-from-melpa-build 26 | # COPY melpa-testing.recipe melpa-testing.recipe 27 | COPY test/MinimalProject test/MinimalProject 28 | COPY test/*.el test/ 29 | COPY test/buttercup-tests test/buttercup-tests 30 | COPY ignored-from-melpa-build/*.el ignored-from-melpa-build/ 31 | COPY test-stuff test-stuff 32 | COPY .git .git 33 | 34 | -------------------------------------------------------------------------------- /test/buttercup-tests/current-symbol/find-implementations-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Find implementations" 4 | (before-each 5 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs")) 6 | 7 | (it "navigates to the only implementation when only one found" 8 | (ot--buffer-contents-and-point-at-$ 9 | "public interface IInter$face {}" 10 | "public class SomeClass : IInterface {}") 11 | 12 | (ot--evaluate-and-wait-for-server-response "(omnisharp-find-implementations)") 13 | (ot--point-should-be-on-a-line-containing "public class SomeClass : IInterface {}")) 14 | 15 | (it "shows a list of implementations when more than one found" 16 | (ot--buffer-contents-and-point-at-$ 17 | "public class Base$Class {}" 18 | "public class SomeClass : BaseClass {}" 19 | "public class SomeClass2 : BaseClass {}") 20 | 21 | (ot--evaluate-and-wait-for-server-response "(omnisharp-find-implementations)") 22 | (ot--switch-to-buffer "* OmniSharp : Implementations *") 23 | (ot--i-should-see "public class SomeClass : BaseClass {}") 24 | (ot--i-should-see "public class SomeClass2 : BaseClass {}"))) 25 | -------------------------------------------------------------------------------- /test/buttercup-tests/current-symbol/find-implementations-with-ido-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Find implementations with ido" 4 | (before-each 5 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 6 | (spy-on completing-read-function)) 7 | 8 | (it "navigates to the only implementation when only one found" 9 | (ot--buffer-contents-and-point-at-$ 10 | "public interface IInter$face {}" 11 | "public class SomeClass : IInterface {}") 12 | 13 | (ot--evaluate-and-wait-for-server-response "(omnisharp-find-implementations-with-ido)") 14 | (ot--point-should-be-on-a-line-containing "public class SomeClass : IInterface {}") 15 | (expect completing-read-function :not :to-have-been-called)) 16 | 17 | (it "lets the user choose one with ido when more than one found" 18 | (ot--buffer-contents-and-point-at-$ 19 | "public interface IInter$face {}" 20 | "public class SomeClass : IInterface {}" 21 | "public class SomeClass2 : IInterface {}") 22 | 23 | (ot--evaluate-and-wait-for-server-response "(omnisharp-find-implementations-with-ido)") 24 | (expect completing-read-function :to-have-been-called))) 25 | -------------------------------------------------------------------------------- /test/buttercup-tests/formatting/formatting-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Format on keystroke" 4 | 5 | (before-each 6 | (ot--open-the-minimal-project-source-file "KeystrokeTest.cs")) 7 | 8 | (it "formats on pressing semicolon" 9 | (ot--buffer-contents-and-point-at-$ 10 | "public class KeystrokeTest" 11 | "{" 12 | " public KeystrokeTest()" 13 | " {" 14 | " var i =1$" 15 | " }") 16 | 17 | (omnisharp-format-on-keystroke ";") 18 | (ot--buffer-should-contain 19 | "public class KeystrokeTest" 20 | "{" 21 | " public KeystrokeTest()" 22 | " {" 23 | " var i = 1;" 24 | " }")) 25 | 26 | (it "formats on pressing closing brace" 27 | (ot--buffer-contents-and-point-at-$ 28 | "public class KeystrokeTest" 29 | "{" 30 | " public KeystrokeTest()" 31 | " {" 32 | " var i =1;$") 33 | 34 | (omnisharp-format-on-keystroke "}") 35 | (ot--buffer-should-contain 36 | "public class KeystrokeTest" 37 | "{" 38 | " public KeystrokeTest()" 39 | " {" 40 | " var i = 1;" 41 | " }"))) 42 | 43 | -------------------------------------------------------------------------------- /ignored-from-melpa-build/load-omnisharp-development-files.el: -------------------------------------------------------------------------------- 1 | ;; You can use this in your emacs session to start developing, as the first 2 | ;; thing you want to do is load the development istance your emacs session. 3 | 4 | ;; So to get started you must 5 | ;; M-x eval-buffer (with this file open) 6 | ;; evaluate the `omnisharp--load-development-files` form at the bottom manually 7 | 8 | (defvar root-directory (-> (f-this-file) 9 | f-parent 10 | f-parent)) 11 | 12 | (defun el-file-or-directory? (f) 13 | (or (f-directory? f) (equal "el" (f-ext f)))) 14 | 15 | (defun omnisharp--files (path) 16 | (let ((path (f-join root-directory path)) 17 | (recursive? t)) 18 | (if (f-directory? path) 19 | (f-files path 'el-file-or-directory? recursive?) 20 | path))) 21 | 22 | (defun omnisharp--load-development-files (&rest paths) 23 | (let ((files (->> paths 24 | (-map 'omnisharp--files) 25 | (-flatten)))) 26 | (-each files 'load-file))) 27 | 28 | (when nil 29 | ;; evaluate this manually 30 | (omnisharp--load-development-files "src" 31 | "features" 32 | "omnisharp.el" 33 | "test")) 34 | -------------------------------------------------------------------------------- /test-stuff/run-melpa-build-test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | set -o pipefail 3 | 4 | # Tests that the program can be installed without an error from melpa. 5 | # 6 | # Will break if dependencies are not configured correctly, in which 7 | # case we should update dependency versions and/or file issues to the 8 | # relative projects. 9 | 10 | if [ -d melpa ]; then 11 | rm -rf melpa 12 | fi 13 | 14 | git clone https://github.com/melpa/melpa 15 | 16 | # Custom recipe that uses the melpa-testing branch instead of the 17 | # usual develop, to showcase a minimal broken setup. 18 | recipeFile=./test-stuff/melpa-testing.recipe 19 | 20 | cat $recipeFile 21 | cp $recipeFile ./melpa/recipes/omnisharp 22 | 23 | cd melpa 24 | make clean 25 | make recipes/omnisharp 26 | 27 | cd .. 28 | 29 | # No cask here. Use a fresh emacs so installation is as natural as possible 30 | homeDir=`mktemp -d` 31 | HOME=$homeDir emacs -Q \ 32 | --eval '(setq user-emacs-directory "./sandbox")' \ 33 | -l package \ 34 | --script ignored-from-melpa-build/melpa-build-test.el 2>&1 | tee installation-output.txt 35 | 36 | # Return value hack. Emacs above does not report the correct exit code. 37 | # Grep returns 0 when the searched line is found, see man grep. 38 | # 39 | # Trying to match this line: 40 | grep "Installation successful lololololol" installation-output.txt 41 | -------------------------------------------------------------------------------- /test/buttercup-tests/navigation/imenu-test.el: -------------------------------------------------------------------------------- 1 | ;; This file is free software; you can redistribute it and/or modify 2 | ;; it under the terms of the GNU General Public License as published by 3 | ;; the Free Software Foundation; either version 3, or (at your option) 4 | ;; any later version. 5 | 6 | ;; This file is distributed in the hope that it will be useful, 7 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | ;; GNU General Public License for more details. 10 | 11 | ;; You should have received a copy of the GNU General Public License 12 | ;; along with this program. If not, see . 13 | 14 | 15 | (describe "Imenu integration" 16 | (it "builds an index of members in the current file" 17 | (ot--open-the-minimal-project-source-file "ImenuTest.cs") 18 | (ot--buffer-contents-and-point-at-$ 19 | "namespace minimal" 20 | "{" 21 | " public class Tar$get {" 22 | " public int Zero = 0;" 23 | " public int One = 2; // haha" 24 | " }" 25 | "}") 26 | 27 | (imenu (-first (-lambda ((name . location-marker)) 28 | (s-equals? name "Zero")) 29 | (omnisharp-imenu-create-index))) 30 | (ot--point-should-be-on-a-line-containing "public int Zero = 0;"))) 31 | -------------------------------------------------------------------------------- /test/buttercup-tests/navigation/navigate-to-solution-file-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 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 | (describe "Navigate to solution file" 17 | (it "asks the user for a file in the current solution to navigate to and goes there" 18 | (-when-let (buffer (get-buffer "MyClass.cs")) 19 | (kill-buffer buffer)) 20 | 21 | (ot--answer-omnisharp--completing-read-with 22 | (lambda (choices) 23 | (--first (s-contains? "MyClass.cs" it) 24 | choices))) 25 | (omnisharp--wait-until-request-completed 26 | (omnisharp-navigate-to-solution-file)) 27 | (ot--there-should-be-a-window-editing-the-file "MyClass.cs"))) 28 | -------------------------------------------------------------------------------- /test/buttercup-tests/navigation/navigate-to-region-test.el: -------------------------------------------------------------------------------- 1 | ;; This file is free software; you can redistribute it and/or modify 2 | ;; it under the terms of the GNU General Public License as published by 3 | ;; the Free Software Foundation; either version 3, or (at your option) 4 | ;; any later version. 5 | 6 | ;; This file is distributed in the hope that it will be useful, 7 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | ;; GNU General Public License for more details. 10 | 11 | ;; You should have received a copy of the GNU General Public License 12 | ;; along with this program. If not, see . 13 | 14 | 15 | (describe "Navigate to region" 16 | (it "asks the user for a region in the current file to navigate to and goes there" 17 | 18 | (ot--open-the-minimal-project-source-file "MyClass.cs") 19 | (ot--buffer-contents-and-point-at-$ 20 | "namespace Test {" 21 | " #region awesome" 22 | " public class Awesome {}" 23 | " #endregion awesome" 24 | "$" 25 | "}") 26 | (ot--answer-omnisharp--completing-read-with 27 | (lambda (choices) 28 | (--first (s-contains? "awesome" it) 29 | choices))) 30 | (omnisharp--wait-until-request-completed 31 | (omnisharp-navigate-to-region)) 32 | (ot--point-should-be-on-a-line-containing "#region awesome"))) 33 | -------------------------------------------------------------------------------- /test/buttercup-tests/solution/solution-errors-test.el: -------------------------------------------------------------------------------- 1 | ;; This file is free software; you can redistribute it and/or modify 2 | ;; it under the terms of the GNU General Public License as published by 3 | ;; the Free Software Foundation; either version 3, or (at your option) 4 | ;; any later version. 5 | 6 | ;; This file is distributed in the hope that it will be useful, 7 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | ;; GNU General Public License for more details. 10 | 11 | ;; You should have received a copy of the GNU General Public License 12 | ;; along with this program. If not, see . 13 | 14 | 15 | (describe "omnisharp-solution-errors" 16 | (it "lists solution errors/warnings in a separate buffer" 17 | (ot--open-the-minimal-project-source-file "MyClass.cs") 18 | (ot--buffer-contents-and-point-at-$ 19 | "using System;" 20 | "namespace minimal" 21 | "{" 22 | " public class MyClass" 23 | " {" 24 | " error-trigger$" 25 | " }" 26 | "}") 27 | 28 | (omnisharp--wait-until-request-completed 29 | (omnisharp-solution-errors)) 30 | 31 | (ot--switch-to-the-window-in-the-buffer "*omnisharp-solution-errors*") 32 | 33 | (ot--buffer-should-contain "MyClass.cs(6,14): error CS1519: Invalid token '-' in class, record, struct, or interface member declaration") 34 | (ot--buffer-should-contain "MyClass.cs(7,5): error CS1519: Invalid token '}' in class, record, struct, or interface member declaration") 35 | (ot--buffer-should-contain "omnisharp-solution-errors: finished"))) 36 | -------------------------------------------------------------------------------- /test/test-helper.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 | ;; http://tuxicity.se/emacs/testing/cask/ert-runner/2013/09/26/unit-testing-in-emacs.html 18 | ;; This file is included in each test run before any test file is 19 | ;; loaded. This is a good place for common test helper functions. 20 | (require 'ert) 21 | (require 'ert-async) 22 | (require 's) 23 | (require 'cl) 24 | 25 | ;; these are run in the omnisharp source code root directory 26 | (add-to-list 'load-path (expand-file-name "./")) 27 | (require 'omnisharp) 28 | (require 'evil) ; some tests test evil functionality specifically 29 | (require 'el-mock) 30 | (require 'noflet) 31 | (require 'buttercup) 32 | 33 | (defmacro with-server-returning (called-api-name return-value &rest test-forms) 34 | "Allows mocking calling the omnisharp-roslyn stdio server to test 35 | callback effects directly, without the need of a running 36 | omnisharp-roslyn process." 37 | `(noflet ((omnisharp--send-command-to-server (_api-name _payload &optional response-handler) 38 | (apply response-handler (list ,return-value)))) 39 | ,@test-forms)) 40 | -------------------------------------------------------------------------------- /test/buttercup-tests/navigation/find-symbols-test.el: -------------------------------------------------------------------------------- 1 | ;; This file is free software; you can redistribute it and/or modify 2 | ;; it under the terms of the GNU General Public License as published by 3 | ;; the Free Software Foundation; either version 3, or (at your option) 4 | ;; any later version. 5 | 6 | ;; This file is distributed in the hope that it will be useful, 7 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | ;; GNU General Public License for more details. 10 | 11 | ;; You should have received a copy of the GNU General Public License 12 | ;; along with this program. If not, see . 13 | 14 | 15 | (describe "Navigate to solution member (find symbols)" 16 | (it "moves point to selected type" 17 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 18 | (ot--buffer-contents-and-point-at-$ 19 | "using System;" 20 | "namespace minimal" 21 | "{" 22 | " public class MyClassContainer" 23 | " {" 24 | " $public MyClass foo;" 25 | " }" 26 | "}") 27 | ;; automatically select the first candidate given to 28 | ;; omnisharp--choose-quickfix-ido. 29 | (ot--answer-omnisharp--completing-read-with 30 | (lambda (choices) 31 | (--first (s-contains? "MyClassContainer" it) 32 | choices))) 33 | 34 | ;; should filter to "foo", defined in this class 35 | (spy-on 'omnisharp--read-string :and-return-value "MyClassC") 36 | 37 | (omnisharp--wait-until-request-completed (omnisharp-navigate-to-solution-member)) 38 | 39 | (ot--point-should-be-on-a-line-containing "public class MyClassContainer") 40 | (ot--i-should-be-in-buffer-name "MyClassContainer.cs"))) 41 | -------------------------------------------------------------------------------- /features-tbd-on-the-server-side/fix-usings.feature: -------------------------------------------------------------------------------- 1 | Feature: Fix usings 2 | In order to code without having to worry about using statements 3 | As a user 4 | I want to automatically insert missing using statements based on the 5 | symbols in the current file 6 | 7 | Background: 8 | Given I open temp file "some-file.cs" 9 | Given The buffer is empty 10 | 11 | Scenario: A single import is added automatically 12 | When I insert: 13 | """ 14 | namespace FixUsingsTest { 15 | public class Awesome { 16 | StringWriter writer; 17 | } 18 | } 19 | """ 20 | And I evaluate the command "(omnisharp-fix-usings)" 21 | Then I should see, ignoring line endings: 22 | """ 23 | using System.IO; 24 | """ 25 | 26 | Then I should see, ignoring line endings: 27 | """ 28 | namespace FixUsingsTest { 29 | public class Awesome { 30 | StringWriter writer; 31 | } 32 | } 33 | """ 34 | 35 | Scenario: Multiple imports let the user choose the import they want manually 36 | When I insert: 37 | """ 38 | namespace mika { 39 | public class test { 40 | class1 classOne; 41 | } 42 | } 43 | 44 | namespace ns1 45 | { 46 | public class class1{} 47 | } 48 | 49 | namespace ns2 50 | { 51 | public class class1{} 52 | } 53 | """ 54 | And I evaluate the command "(omnisharp-fix-usings)" 55 | When I switch to the existing buffer "* OmniSharp : Ambiguous unresolved symbols *" 56 | Then I should see, ignoring line endings: 57 | """ 58 | These results are ambiguous. You can run 59 | (omnisharp-run-code-action-refactoring) when point is on them to see 60 | options for fixing them. 61 | """ 62 | -------------------------------------------------------------------------------- /test/buttercup-tests/flycheck/flycheck-test.el: -------------------------------------------------------------------------------- 1 | ;; -*- mode: Emacs-Lisp; lexical-binding: t; -*- 2 | ;; License: GNU General Public License version 3, or (at your option) any later version 3 | 4 | ;; TODO: this is not working since the update to .net core 2.1 of the unit test project 5 | ;; I couldn't figure easily why, disabled for now as the functionality actually works 6 | (xdescribe "Flycheck integration" 7 | (it "integrates flycheck with omnisharp" 8 | (ot--open-the-minimal-project-source-file "FlycheckTest.cs") 9 | (ot--buffer-contents-and-point-at-$ 10 | "$public class MyClass { 11 | DoesNotExist foo; 12 | }") 13 | 14 | ;; required so the correct checker can be found by flycheck 15 | (omnisharp-mode t) 16 | (flycheck-mode) 17 | 18 | ;; make all flycheck calls wait until the server response has been handled 19 | (let ((original-function (symbol-function 'omnisharp--flycheck-start))) 20 | (spy-on 'omnisharp--flycheck-start :and-call-fake 21 | (lambda (&rest args) 22 | (omnisharp--wait-until-request-completed 23 | (apply original-function args))))) 24 | 25 | (flycheck-buffer) 26 | ;; when running this test from the command line, this function is 27 | ;; not called if a spy is not defined for it 28 | (expect 'omnisharp--flycheck-start :to-have-been-called) 29 | 30 | ;; open error list and verify its contents 31 | (flycheck-list-errors) 32 | 33 | (ot--switch-to-the-window-in-the-buffer "*Flycheck errors*") 34 | (ot--point-should-be-on-a-line-containing 35 | (concat "The type or namespace name 'DoesNotExist' could not be found" 36 | " (are you missing a using directive or an assembly reference?)")) 37 | 38 | ;; navigating to the next error will take the user to the correct place 39 | (ot--switch-to-buffer "FlycheckTest.cs") 40 | (flycheck-next-error) 41 | (expect (thing-at-point 'word) :to-equal "DoesNotExist"))) 42 | -------------------------------------------------------------------------------- /ignored-from-melpa-build/prodigy-config.el: -------------------------------------------------------------------------------- 1 | ;; If you use the prodigy plugin for emacs, you can quickly adapt this 2 | ;; file to set up emacs as an interface to start background processes 3 | ;; and run test for this project. 4 | ;; 5 | ;; To use this file as such do the following: 6 | ;; - Configure the file and set the paths right 7 | ;; - Load this file's code into emacs somehow, e.g. from your init file do 8 | ;; (load-file "/home/mika/git/omnisharp-emacs/ignored-from-melpa-build/prodigy-example-config.el") 9 | ;; - Then run M-x prodigy. You will be shown the services defined in 10 | ;; this file. Start a service with "s" and view its output with "$" 11 | ;; (once it starts generating output). For more info, see the 12 | ;; prodigy site below 13 | ;; 14 | ;; For prodigy: 15 | ;; https://github.com/rejeep/prodigy.el 16 | (require 'prodigy) 17 | 18 | ;; omnisharp-emacs development hacks 19 | (setenv "PATH" (concat (getenv "PATH") 20 | ":/home/mika/.cask/bin" 21 | ":/home/mika/bin/")) 22 | 23 | (defmacro def-omnisharp-service (name command) 24 | (let ((omni-dir "/home/mika/git/omnisharp-emacs/")) 25 | `(prodigy-define-service 26 | :name ,name 27 | :command (concat ,omni-dir ,command) 28 | :cwd ,omni-dir 29 | :stop-signal 'kill 30 | :kill-process-buffer-on-stop t 31 | :truncate-output 200 32 | :tags '(omnisharp)))) 33 | 34 | (def-omnisharp-service "OmniSharpServer for integration tests" 35 | "start-omnisharp-server-for-integration-tests.sh") 36 | 37 | (def-omnisharp-service "omnisharp-emacs integration tests" 38 | "run-integration-tests.sh") 39 | 40 | (def-omnisharp-service "omnisharp-emacs single integration test" 41 | "run-single-integration-test.sh") 42 | 43 | (def-omnisharp-service "omnisharp-emacs unit tests" 44 | "run-tests.sh") 45 | 46 | (def-omnisharp-service "omnisharp-emacs installation test" 47 | "run-melpa-build-test.sh") 48 | 49 | (add-to-list 'evil-emacs-state-modes 'prodigy-mode) 50 | (provide 'prodigy-config) 51 | -------------------------------------------------------------------------------- /test/buttercup-tests/current-symbol/rename-symbol-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Rename symbol" 4 | (it "renames a symbol referenced only in a single file" 5 | (ot--open-the-minimal-project-source-file "RenameFileTest.cs") 6 | (ot--buffer-contents-and-point-at-$ 7 | "using System;" 8 | "namespace minimal" 9 | "{" 10 | " public class OldClass {}" 11 | " public class OtherClass {" 12 | " Old$Class foo; // rename here" 13 | " }" 14 | "}") 15 | 16 | ;; rename to: 17 | (spy-on 'read-string :and-return-value "OldClassChanged") 18 | (omnisharp-rename) 19 | (ot--point-should-be-on-line-number 6) 20 | 21 | (ot--i-should-see 22 | "using System;" 23 | "namespace minimal" 24 | "{" 25 | " public class OldClassChanged {}" 26 | " public class OtherClass {" 27 | " OldClassChanged foo; // rename here" 28 | " }" 29 | "}")) 30 | 31 | (it "renames a symbol referenced in multiple files" 32 | (ot--open-the-minimal-project-source-file "MyClass.cs") 33 | (ot--buffer-contents-and-point-at-$ 34 | "using System;" 35 | "namespace minimal$" 36 | "{" 37 | " public class MyClass {}" 38 | "}") 39 | 40 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 41 | (ot--buffer-contents-and-point-at-$ 42 | "using System;" 43 | "namespace minimal" 44 | "{" 45 | " public class MyClassContainer" 46 | " {" 47 | " public My$Class foo;" 48 | " }" 49 | "}") 50 | 51 | (spy-on 'read-string :and-return-value "MyClass2") 52 | (omnisharp-rename) 53 | 54 | (ot--buffer-should-contain 55 | "using System;" 56 | "namespace minimal" 57 | "{" 58 | " public class MyClassContainer" 59 | " {" 60 | " public MyClass2 foo;" 61 | " }" 62 | "}") 63 | 64 | (ot--switch-to-buffer "MyClass.cs") 65 | (ot--buffer-should-contain 66 | "using System;" 67 | "namespace minimal" 68 | "{" 69 | " public class MyClass2 {}" 70 | "}"))) 71 | -------------------------------------------------------------------------------- /test/buttercup-tests/auto-complete/auto-complete-company-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Auto-complete using the company interface" 4 | 5 | (before-each 6 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 7 | (omnisharp-mode t) 8 | (require 'company) 9 | (company-mode t) 10 | (eval-after-load 'company 11 | '(add-to-list 'company-backends 'company-omnisharp))) 12 | 13 | (it "completes a member in the same file" 14 | (ot--buffer-contents-and-point-at-$ 15 | "namespace Test { 16 | public class Awesome { 17 | StringWriter writer; 18 | public Awesome() { 19 | wri$ 20 | } 21 | } 22 | }") 23 | 24 | (company-complete) 25 | 26 | (ot--buffer-should-contain 27 | "namespace Test { 28 | public class Awesome { 29 | StringWriter writer; 30 | public Awesome() { 31 | writer 32 | } 33 | } 34 | }")) 35 | 36 | (it "completes after dot" 37 | (ot--buffer-contents-and-point-at-$ 38 | "using System; 39 | namespace Test { 40 | public class Awesome { 41 | public Awesome() { 42 | Console.$ 43 | } 44 | } 45 | }") 46 | 47 | (expect (ot--get-completions) :to-contain "WriteLine()")) 48 | 49 | (it "completes a function that has parameters using snippets" 50 | (ot--buffer-contents-and-point-at-$ 51 | "namespace Test { 52 | public class Awesome { 53 | public Awesome() { 54 | object.Equa$ 55 | } 56 | } 57 | }") 58 | 59 | (company-complete) 60 | 61 | (ot--keyboard-input 62 | ;; will complete the current line to this: 63 | ;; object.Equals(object objA, object objB) 64 | ;; 65 | ;; Typing anything now will replace the first parameter 66 | (ot--type "this") 67 | (ot--press-key "TAB") 68 | ;; now fill the second parameter 69 | (ot--type "new object()") 70 | (ot--press-key "TAB")) 71 | 72 | (ot--buffer-should-contain "object.Equals(t, object objB)"))) 73 | -------------------------------------------------------------------------------- /doc/example-config-for-evil-mode.el: -------------------------------------------------------------------------------- 1 | ;; Example evil-mode config 2 | 3 | (evil-define-key 'insert omnisharp-mode-map (kbd "M-.") 'omnisharp-auto-complete) 4 | (evil-define-key 'normal omnisharp-mode-map (kbd "") 'omnisharp-go-to-definition) 5 | (evil-define-key 'normal omnisharp-mode-map (kbd "g u") 'omnisharp-find-usages) 6 | (evil-define-key 'normal omnisharp-mode-map (kbd "g I") 'omnisharp-find-implementations) ; g i is taken 7 | (evil-define-key 'normal omnisharp-mode-map (kbd "g o") 'omnisharp-go-to-definition) 8 | (evil-define-key 'normal omnisharp-mode-map (kbd "g r") 'omnisharp-run-code-action-refactoring) 9 | (evil-define-key 'normal omnisharp-mode-map (kbd "g f") 'omnisharp-fix-code-issue-at-point) 10 | (evil-define-key 'normal omnisharp-mode-map (kbd "g F") 'omnisharp-fix-usings) 11 | (evil-define-key 'normal omnisharp-mode-map (kbd "g R") 'omnisharp-rename) 12 | (evil-define-key 'normal omnisharp-mode-map (kbd ", i") 'omnisharp-current-type-information) 13 | (evil-define-key 'normal omnisharp-mode-map (kbd ", I") 'omnisharp-current-type-documentation) 14 | (evil-define-key 'insert omnisharp-mode-map (kbd ".") 'omnisharp-add-dot-and-auto-complete) 15 | (evil-define-key 'normal omnisharp-mode-map (kbd ", n t") 'omnisharp-navigate-to-current-file-member) 16 | (evil-define-key 'normal omnisharp-mode-map (kbd ", n s") 'omnisharp-navigate-to-solution-member) 17 | (evil-define-key 'normal omnisharp-mode-map (kbd ", n f") 'omnisharp-navigate-to-solution-file-then-file-member) 18 | (evil-define-key 'normal omnisharp-mode-map (kbd ", n F") 'omnisharp-navigate-to-solution-file) 19 | (evil-define-key 'normal omnisharp-mode-map (kbd ", n r") 'omnisharp-navigate-to-region) 20 | (evil-define-key 'normal omnisharp-mode-map (kbd "") 'omnisharp-show-last-auto-complete-result) 21 | (evil-define-key 'insert omnisharp-mode-map (kbd "") 'omnisharp-show-last-auto-complete-result) 22 | (evil-define-key 'normal omnisharp-mode-map (kbd ",.") 'omnisharp-show-overloads-at-point) 23 | (evil-define-key 'normal omnisharp-mode-map (kbd ",rl") 'recompile) 24 | 25 | (evil-define-key 'normal omnisharp-mode-map (kbd ",rt") 26 | (lambda() (interactive) (omnisharp-unit-test "single"))) 27 | 28 | (evil-define-key 'normal omnisharp-mode-map 29 | (kbd ",rf") 30 | (lambda() (interactive) (omnisharp-unit-test "fixture"))) 31 | 32 | (evil-define-key 'normal omnisharp-mode-map 33 | (kbd ",ra") 34 | (lambda() (interactive) (omnisharp-unit-test "all"))) 35 | 36 | ;; Speed up auto-complete on mono drastically. This comes with the 37 | ;; downside that documentation is impossible to fetch. 38 | (setq omnisharp-auto-complete-want-documentation nil) 39 | -------------------------------------------------------------------------------- /omnisharp-http-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--get-host () 19 | "Makes sure omnisharp-host is ended by / " 20 | (if (string= (substring omnisharp-host -1 ) "/") 21 | omnisharp-host 22 | (concat omnisharp-host "/"))) 23 | 24 | (defun omnisharp--get-api-url (api-name) 25 | (concat (omnisharp--get-host) api-name)) 26 | 27 | (defun omnisharp-post-http-message (url callback &optional params async) 28 | "Post http request to server. Return result." 29 | (omnisharp--submit-request (omnisharp--get-api-url url) callback params async)) 30 | 31 | (defun omnisharp--submit-request (url callback &optional params async) 32 | (if (require 'request nil 'noerror) 33 | (lexical-let* ((c callback)) 34 | (request url 35 | :type "POST" 36 | :parser 'json-read 37 | :sync (not async) 38 | :data (json-encode params) 39 | :error (cl-function (lambda (&rest args &key error-thrown &allow-other-keys) 40 | (message "Error from %s : %S" url error-thrown))) 41 | :complete 42 | (lambda (&rest _) 43 | (when omnisharp-debug 44 | (message "Request completed"))) 45 | :success (cl-function (lambda (&key data &allow-other-keys) 46 | (progn 47 | (when c 48 | (funcall c data)) 49 | (when omnisharp-debug 50 | (message "Request succeeded")) 51 | ))) 52 | :status-code '((404 . (lambda (&rest _) (message (format "Endpoint %s does not exist." url)))) 53 | (500 . (lambda (&rest _) (message (format "Error from %s." url)))) 54 | ))) 55 | (message "ERROR: You must install 'request-deferred' package"))) 56 | 57 | (provide 'omnisharp-http-utils) 58 | -------------------------------------------------------------------------------- /doc/server-installation.md: -------------------------------------------------------------------------------- 1 | # Installation of the omnisharp-roslyn server application 2 | This emacs package requires the [omnisharp-roslyn](https://github.com/OmniSharp/omnisharp-roslyn) server program. 3 | 4 | You have three options here: 5 | * You can use M-x `omnisharp-install-server` to install omnisharp-server binary automatically. 6 | 7 | * *NOTE: On Windows, this command requires PowerShell v5+ to be installed 8 | – see [omnisharp-emacs#275](https://github.com/OmniSharp/omnisharp-emacs/issues/275).* 9 | 10 | * *NOTE 2*: On macOS and Linux omnisharp server binary requires 11 | [mono](http://www.mono-project.com/) to be installed on your system. 12 | 13 | * Download and extract server binaries 14 | manually and then point `omnisharp-server-executable-path` variable to the binary. 15 | 16 | * Build the server yourself from the source. 17 | Building instructions are detailed in 18 | [omnisharp-roslyn building page](https://github.com/OmniSharp/omnisharp-roslyn#building). 19 | 20 | ## Manual installation on macOS with brew 21 |
22 | brew install omnisharp/omnisharp-roslyn/omnisharp-mono
23 | 
24 | 25 | Then you need to set the `omnisharp-server-executable-path`: 26 | 27 | ```lisp 28 | (setq omnisharp-server-executable-path "/usr/local/bin/omnisharp") 29 | ``` 30 | 31 | ## Manual installation on Linux 32 | Extract binary from [omnisharp-roslyn releases page](https://github.com/OmniSharp/omnisharp-roslyn/releases). 33 | 34 | Then you need to set the `omnisharp-server-executable-path`: 35 | 36 | ```lisp 37 | (setq omnisharp-server-executable-path "") 38 | ``` 39 | 40 | ## Manual installation on Windows (non-Cygwin) 41 | Use binary from [omnisharp-roslyn releases page](https://github.com/OmniSharp/omnisharp-roslyn/releases). 42 | 43 | *NOTE: For the moment you HAVE to use the `omnisharp-win-x86.zip` bundle as -x64- one makes emacs 44 | to crash in `src/w32proc.c:w32_executable_type`.* See https://github.com/OmniSharp/omnisharp-emacs/issues/315 45 | 46 | Then you need to set the `omnisharp-server-executable-path` the path 47 | to where you have extracted server file, e.g.: 48 | 49 | ```lisp 50 | (setq omnisharp-server-executable-path "C:\\Bin\\omnisharp-roslyn\\OmniSharp.exe") 51 | ``` 52 | 53 | ## Manual installation on windows (with Cygwin) 54 | Spawning omnisharp-roslyn from cygwin on the microsoft .net framework will result in hangs as described in: 55 | 56 | https://cygwin.com/ml/cygwin/2013-12/msg00345.html 57 | 58 | To work around this, you can run it on mono. 59 | 60 | - Install latest mono runtime http://www.mono-project.com/download/ 61 | - Download and unpack mono release from https://github.com/OmniSharp/omnisharp-roslyn/releases 62 | - Create an `omnisharp` shell script like: 63 | ``` 64 | #!/bin/sh 65 | exec [cygwin path to mono]/mono "$(cygpath -wa [cygwin path to omnisharp]/OmniSharp.exe)" "$@" 66 | ``` 67 | - Set `omnisharp-server-executable-path` to the shell script. 68 | -------------------------------------------------------------------------------- /test/buttercup-tests/auto-complete/auto-complete-popup-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | 3 | (describe "Auto-complete using the popup interface" 4 | (before-each 5 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 6 | (ot--set omnisharp--auto-complete-display-backend 'popup)) 7 | 8 | (after-each 9 | (require 'yasnippet) 10 | (yas-minor-mode nil)) 11 | 12 | (it "completes a member in the same file" 13 | (ot--buffer-contents-and-point-at-$ 14 | "namespace Test {" 15 | " public class Awesome {" 16 | " StringWriter writer;" 17 | " public Awesome() {" 18 | " wri$" 19 | " }" 20 | " }" 21 | "}") 22 | 23 | (ot--keyboard-input 24 | (ot--meta-x-command "omnisharp-auto-complete") 25 | ;; A pop-up.el menu is shown. Complete the first candidate. 26 | (ot--press-key "RET")) 27 | 28 | (ot--buffer-should-contain 29 | "namespace Test {" 30 | " public class Awesome {" 31 | " StringWriter writer;" 32 | " public Awesome() {" 33 | " writer" 34 | " }" 35 | " }" 36 | "}")) 37 | 38 | (it "when yasnippet is loaded, completes a function that has parameters using snippets" 39 | (yas-minor-mode) 40 | (ot--buffer-contents-and-point-at-$ 41 | "namespace Test {" 42 | " public class Awesome {" 43 | " public Awesome() {" 44 | " object.Equa$" 45 | " }" 46 | " }" 47 | "}") 48 | 49 | (ot--keyboard-input 50 | (ot--press-key "M-x") 51 | (ot--type "omnisharp-auto-complete") 52 | (ot--press-key "RET") 53 | ;; A pop-up.el menu is shown. Complete the first candidate. 54 | (ot--press-key "RET") 55 | 56 | ;; yasnippet will complete the current line to this: 57 | ;; object.Equals(object objA, object objB) 58 | ;; 59 | ;; Typing anything now will replace the first parameter 60 | (ot--type "this") 61 | (ot--press-key "TAB") 62 | ;; now fill the second parameter 63 | (ot--type "new object()") 64 | (ot--press-key "TAB")) 65 | 66 | (ot--buffer-should-contain "object.Equals(this, new object())") 67 | 68 | ;; if not done, other tests will fail due to "something something 69 | ;; yas overlay is active" 70 | (kill-buffer "MyClassContainer.cs"))) 71 | 72 | (describe "auto-complete's completion source" 73 | (it "provides valid completions as an auto-complete source" 74 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 75 | (ot--buffer-contents-and-point-at-$ 76 | "namespace Test {" 77 | " public class Awesome {" 78 | " public Awesome() {" 79 | " object.Equa$" 80 | " }" 81 | " }" 82 | "}") 83 | (expect 84 | (popup-item-value 85 | (-first-item 86 | (omnisharp--get-auto-complete-result-in-popup-format))) 87 | :to-be-truthy))) 88 | -------------------------------------------------------------------------------- /test/buttercup-tests/solution/code-actions/fix-code-issue-test.el: -------------------------------------------------------------------------------- 1 | ;; License: GNU General Public License version 3, or (at your option) any later version 2 | ;; 3 | ;; Test a few different kinds of code actions to see we can support 4 | ;; each one in a sensible manner. 5 | (describe "Fix code issue" 6 | (before-each (ot--open-the-minimal-project-source-file "MyClass.cs")) 7 | (it "can act on a simple part of the buffer (using System;)" 8 | (ot--buffer-contents-and-point-at-$ 9 | "public class Class1" 10 | "{" 11 | " public void Whatever()" 12 | " {" 13 | " Gu$id.NewGuid();" 14 | " }" 15 | "}") 16 | (ot--answer-omnisharp--completing-read-with (lambda (choices) "using System;")) 17 | (omnisharp--wait-until-request-completed (omnisharp-run-code-action-refactoring)) 18 | (ot--buffer-should-contain "using System;")) 19 | 20 | (it "can operate on the current region (Extract method)" 21 | (ot--buffer-contents-and-region 22 | "public class Class1" 23 | "{" 24 | " public void Whatever()" 25 | " {" 26 | " (region-starts-here)int$ i = 1;" 27 | " int i2 = 2;(region-ends-here)" 28 | " }" 29 | "}") 30 | 31 | (ot--answer-omnisharp--completing-read-with 32 | (lambda (choices) 33 | (--first (s-contains? "Extract method" it) 34 | choices))) 35 | 36 | (omnisharp--wait-until-request-completed (omnisharp-run-code-action-refactoring)) 37 | 38 | (ot--buffer-should-contain 39 | "public class Class1" 40 | "{" 41 | " public void Whatever()" 42 | " {" 43 | " NewMethod();" 44 | " }" 45 | "" 46 | " private static void NewMethod()" 47 | " {" 48 | " int i = 1;" 49 | " int i2 = 2;" 50 | " }" 51 | "}")) 52 | 53 | (xit "can create new files (Generate class in new file)" 54 | ;; 2016-05-09 I cannot create a new type. The server doesn't support this yet. 55 | ;; https://github.com/OmniSharp/omnisharp-roslyn/commit/63c9eacf2f145ef20b642b6b11431f38e22bb99a#diff-8ff9938e7aa9073d7e49d52e84bacdaaR162 56 | (ot--delete-the-minimal-project-source-file "MyNewClass.cs") 57 | (ot--buffer-contents-and-point-at-$ 58 | "namespace MyNamespace" 59 | "{" 60 | " public class Class1" 61 | " {" 62 | " public void Whatever()" 63 | " {" 64 | " MyNew$Class.DoSomething();" 65 | " }" 66 | " }" 67 | "}") 68 | (ot--answer-omnisharp--completing-read-with 69 | (lambda (choices) 70 | (--first (equal it 71 | "Generate class for 'MyNewClass' in 'MyNamespace' (in new file)") 72 | choices))) 73 | (omnisharp--wait-until-request-completed (omnisharp-run-code-action-refactoring)) 74 | (ot--there-should-be-a-window-editing-the-file "MyNewClass.cs") 75 | (ot--switch-to-buffer "MyNewClass.cs") 76 | (ot--buffer-should-contain 77 | "namespace MyNamespace" 78 | "{" 79 | " internal class MyNewClass" 80 | " {" 81 | " }" 82 | "}"))) 83 | -------------------------------------------------------------------------------- /omnisharp-server-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--start-omnisharp-server (no-autodetect) 20 | "Actual implementation for autoloaded omnisharp-start-omnisharp-server. 21 | 22 | Will query user for a path to project/solution file to start the server with." 23 | (let ((server-executable-path (omnisharp--resolve-omnisharp-server-executable-path)) 24 | (project-root (omnisharp--project-root))) 25 | (if server-executable-path 26 | (if (and (not no-autodetect) 27 | project-root 28 | (file-directory-p project-root)) 29 | (omnisharp--do-server-start project-root) 30 | (let ((project-root (read-directory-name "Project root to use with OmniSharp: "))) 31 | (if (file-directory-p project-root) 32 | (omnisharp--do-server-start project-root) 33 | (error (format "Path does not lead to a directory: %s" project-root)))))))) 34 | 35 | (defun omnisharp--stop-server () 36 | "Actual implementation for autoloaded omnisharp-stop-server" 37 | (unless (equal nil omnisharp--server-info) 38 | (kill-process (cdr (assoc :process omnisharp--server-info))))) 39 | 40 | (defun omnisharp--reload-solution () 41 | "Actual implementation for autoloaded omnisharp-reload-solution" 42 | (if (and (not (equal nil omnisharp--last-project-path)) 43 | (not (equal nil omnisharp--server-info))) 44 | (progn 45 | (setq omnisharp--restart-server-on-stop t) 46 | (kill-process (cdr (assoc :process omnisharp--server-info)))) 47 | (message "Cannot reload project in Omnisharp - no project previously loaded"))) 48 | 49 | (defun omnisharp--check-alive-status () 50 | "Actual implementation for autoloaded omnisharp-check-alive-status" 51 | (omnisharp--send-command-to-server 52 | "checkalivestatus" 53 | nil 54 | #'omnisharp--check-alive-status-worker)) 55 | 56 | (defun omnisharp--check-alive-status-worker (alive?) 57 | (if alive? 58 | (message "Server is alive and well. Happy coding!") 59 | (message "Server is not alive"))) 60 | 61 | (defun omnisharp--check-ready-status () 62 | "Actual implementation for autoloaded omnisharp--check-ready-status" 63 | (omnisharp--send-command-to-server 64 | "checkreadystatus" 65 | nil 66 | (lambda (ready?) 67 | (if ready? 68 | (message "Server is ready") 69 | (message "Server is not ready yet"))))) 70 | 71 | (provide 'omnisharp-server-actions) 72 | -------------------------------------------------------------------------------- /test/buttercup-tests/navigation/go-to-definition-test.el: -------------------------------------------------------------------------------- 1 | ;; This file is free software; you can redistribute it and/or modify 2 | ;; it under the terms of the GNU General Public License as published by 3 | ;; the Free Software Foundation; either version 3, or (at your option) 4 | ;; any later version. 5 | 6 | ;; This file is distributed in the hope that it will be useful, 7 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | ;; GNU General Public License for more details. 10 | 11 | ;; You should have received a copy of the GNU General Public License 12 | ;; along with this program. If not, see . 13 | 14 | 15 | (describe "Go to definition" 16 | (it "goes to definition in the same file" 17 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 18 | (ot--buffer-contents-and-point-at-$ 19 | "using System;" 20 | "namespace minimal" 21 | "{" 22 | " public class Target {}" 23 | " public class JumpSite {" 24 | " Target$ foo; // go to definition from here" 25 | " }" 26 | "}") 27 | 28 | (omnisharp--wait-until-request-completed (omnisharp-go-to-definition)) 29 | (ot--point-should-be-on-a-line-containing "public class Target {}")) 30 | 31 | 32 | (it "goes to a member defined in another file" 33 | ;; We have to let the server know the contents of the files before 34 | ;; doing anything, otherwise the contents might not be what they 35 | ;; are on disk 36 | (ot--open-the-minimal-project-source-file "MyClass.cs") 37 | (ot--buffer-contents-and-point-at-$ 38 | "using System;" 39 | "namespace minimal" 40 | "{" 41 | " public class MyClass" 42 | " {" 43 | " public MyClass ()" 44 | " {" 45 | " }$" 46 | " }" 47 | "}") 48 | 49 | (ot--open-the-minimal-project-source-file "MyClassContainer.cs") 50 | (ot--buffer-contents-and-point-at-$ 51 | "using System;" 52 | "namespace minimal" 53 | "{" 54 | " public class MyClassContainer" 55 | " {" 56 | " public $MyClass foo;" 57 | " }" 58 | "}") 59 | (omnisharp--wait-until-request-completed (omnisharp-go-to-definition-other-window)) 60 | 61 | (ot--switch-to-the-window-in-the-buffer "MyClass.cs") 62 | (ot--point-should-be-on-a-line-containing "public class MyClass")) 63 | 64 | (it "goes to a member defined in metadata" 65 | (ot--open-the-minimal-project-source-file "MyClass.cs") 66 | (ot--buffer-contents-and-point-at-$ 67 | "using System;" 68 | "namespace minimal" 69 | "{" 70 | " public class MyClass" 71 | " {" 72 | " public st$ring foo;" 73 | " }" 74 | "}") 75 | 76 | (omnisharp-go-to-definition) 77 | (ot--wait-until-all-requests-completed) 78 | 79 | ;; TODO: for some reason I need to set current buffer from window list 80 | ;; with with-current-buffer.. 81 | (with-current-buffer (car (mapcar #'window-buffer (window-list))) 82 | (ot--i-should-be-in-buffer-name "*omnisharp-metadata:MinimalProject:netstandard:System.String*") 83 | (ot--point-should-be-on-a-line-containing "public sealed class String")))) 84 | -------------------------------------------------------------------------------- /omnisharp-helm-integration.el: -------------------------------------------------------------------------------- 1 | ;; -*- mode: Emacs-Lisp; 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 | (when (require 'helm-grep nil 'noerror) 19 | ;;; Helm usages 20 | (defvar omnisharp-helm-usage-candidates nil) 21 | 22 | (defun omnisharp--helm-usage-transform-candidate (candidate) 23 | "Convert a quickfix entry into helm output" 24 | (cons 25 | (format "%s(%s): %s" 26 | (propertize (file-name-nondirectory 27 | (omnisharp--get-filename candidate)) 28 | 'face 'helm-grep-file) 29 | (propertize (number-to-string (cdr (assoc 'Line candidate))) 30 | 'face 'helm-grep-lineno) 31 | (cdr (assoc 'Text candidate))) 32 | candidate)) 33 | 34 | (defun omnisharp--helm-got-usages (quickfixes) 35 | (setq omnisharp-helm-usage-candidates (mapcar 'omnisharp--helm-usage-transform-candidate quickfixes)) 36 | (helm :sources (helm-make-source "Omnisharp - Symbol Usages" 'helm-source-sync 37 | :candidates omnisharp-helm-usage-candidates 38 | :action 'omnisharp--helm-jump-to-candidate) 39 | :truncate-lines t 40 | :buffer omnisharp--find-usages-buffer-name)) 41 | 42 | (defun omnisharp-helm-find-usages () 43 | "Find usages for the symbol under point using Helm" 44 | (interactive) 45 | (message "Helm Finding usages...") 46 | (omnisharp--send-command-to-server 47 | "findusages" 48 | (omnisharp--get-request-object) 49 | (-lambda ((&alist 'QuickFixes quickfixes)) 50 | (omnisharp--helm-got-usages quickfixes)))) 51 | 52 | (defun omnisharp--helm-jump-to-candidate (json-result) 53 | (omnisharp-go-to-file-line-and-column json-result) 54 | (helm-highlight-current-line)) 55 | 56 | ;;; Helm find symbols 57 | (defun omnisharp-helm-find-symbols () 58 | (interactive) 59 | (helm :sources (helm-make-source "Omnisharp - Find Symbols" 'helm-source-sync 60 | :action 'omnisharp--helm-jump-to-candidate 61 | :volatile t 62 | :candidates 'omnisharp--helm-find-symbols-candidates) 63 | :truncate-lines t)) 64 | 65 | (defun omnisharp--helm-find-symbols-candidates () 66 | (if (string= helm-pattern "") 67 | ;; we want to wait for at least one char before we trigger 68 | ;; server search because listing all the symbols can take a lot 69 | ;; of time on large projects 70 | nil 71 | 72 | (let (candidates) 73 | (omnisharp--send-command-to-server-sync 74 | "findsymbols" 75 | `((Filter . ,helm-pattern)) 76 | (-lambda ((&alist 'QuickFixes quickfixes)) 77 | (setq candidates 78 | (-map 'omnisharp--helm-find-symbols-transform-candidate 79 | quickfixes)))) 80 | candidates))) 81 | 82 | (defun omnisharp--helm-find-symbols-transform-candidate (candidate) 83 | "Convert a quickfix entry into helm output" 84 | (cons 85 | (format "%s : %s(%s)" 86 | (propertize (nth 0 (split-string (cdr (assoc 'Text candidate)) "(")) 87 | 'face 'helm-grep-match) 88 | (propertize (omnisharp--get-filename candidate) 89 | 'face 'helm-grep-file) 90 | (propertize (number-to-string (cdr (assoc 'Line candidate))) 91 | 'face 'helm-grep-lineno)) 92 | candidate))) 93 | 94 | (provide 'omnisharp-helm-integration) 95 | -------------------------------------------------------------------------------- /omnisharp-format-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-code-format-entire-file () 19 | "Format the code in the current file. Replaces the file contents 20 | with the formatted result." 21 | (interactive) 22 | (omnisharp--send-command-to-server-sync 23 | "codeformat" 24 | (omnisharp--get-request-object) 25 | (let ((current-file (buffer-file-name))) 26 | (-lambda ((&alist 'Buffer new-buffer-contents)) 27 | (omnisharp--set-buffer-contents-to 28 | current-file 29 | new-buffer-contents 30 | (line-number-at-pos) 31 | (omnisharp--current-column)))))) 32 | 33 | (defun omnisharp-code-format-region () 34 | "Format the code in the current region." 35 | (interactive) 36 | (let ((request (-concat (omnisharp--get-request-object) 37 | `((EndLine . ,(omnisharp--region-end-line)) 38 | (EndColumn . ,(omnisharp--region-end-column))))) 39 | (buffer (current-buffer))) 40 | ;; The server refers to the start Line and Column in this 41 | ;; case. Replace the ones that refer to point 42 | (setcdr (assoc 'Line request) (omnisharp--region-start-line)) 43 | (setcdr (assoc 'Column request) (omnisharp--region-start-column)) 44 | 45 | (if (not mark-active) 46 | (message "Need to select something before trying to format the region") 47 | (omnisharp--send-command-to-server-sync 48 | "formatRange" 49 | request 50 | (-lambda ((&alist 'Changes text-changes)) 51 | (--map (omnisharp--apply-text-change-to-buffer it buffer) 52 | text-changes)))))) 53 | 54 | (defun omnisharp-format-on-keystroke (char) 55 | "Formats the current block as you type `;` or `}`. 56 | 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 | ![](pics/company-mode-popup-complex.png) 74 | 75 | Pressing F1 with a candidate selected in the the company-mode popup 76 | shows a buffer with documentation. 77 | 78 | ![](pics/company-mode-doc-buffer.png) 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 | ![](pics/auto-complete-popup.png) 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 | ![](pics/auto-complete-popup-documentation.png) 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 | ![](pics/auto-complete-ido.png) 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 | ![](pics/navigate-to-type-in-current-file.png) 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 | ![](pics/navigate-to-current-type-member.png) 125 | 126 | ### Rename 127 | Renaming suggests the current type as a basis. 128 | 129 | ![](pics/rename.png) 130 | 131 | ### Overriding members 132 | When invoked, displays a list of possible override targets. 133 | 134 | ![](pics/override-suggestions.png) 135 | 136 | When a target is chosen, a stub member is inserted. 137 | 138 | ![](pics/override-result.png) 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 | ![](pics/refactoring-suggestions.png) 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 | ![](pics/build-solution-in-compilation-buffer.png) 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 | ![](pics/syntax-error-flycheck.png) 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 | ![](pics/eldoc.png) 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 | ![](pics/helm-imenu.png) 192 | 193 | There's also 'omnisharp-helm-find-usages', which allows you to easily 194 | navigate to references in your project: 195 | 196 | ![](pics/omnisharp-helm-find-usages.png) 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 | ![](pics/omnisharp-helm-find-symbols.png) 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 | ![](pics/tests.png) 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 | [![MELPA](https://melpa.org/packages/omnisharp-badge.svg)](https://melpa.org/#/omnisharp) 15 | [![MELPA Stable](https://stable.melpa.org/packages/omnisharp-badge.svg)](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 | --------------------------------------------------------------------------------