ruby-lsp 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -1
  3. data/.github/workflows/publish_docs.yml +32 -0
  4. data/.rubocop.yml +25 -0
  5. data/CHANGELOG.md +23 -0
  6. data/Gemfile +8 -4
  7. data/Gemfile.lock +64 -13
  8. data/README.md +58 -1
  9. data/Rakefile +5 -0
  10. data/VERSION +1 -1
  11. data/bin/tapioca +29 -0
  12. data/dev.yml +3 -0
  13. data/exe/ruby-lsp +19 -3
  14. data/lib/ruby-lsp.rb +2 -0
  15. data/lib/ruby_lsp/cli.rb +23 -7
  16. data/lib/ruby_lsp/document.rb +98 -6
  17. data/lib/ruby_lsp/handler.rb +119 -18
  18. data/lib/ruby_lsp/internal.rb +7 -0
  19. data/lib/ruby_lsp/requests/base_request.rb +19 -5
  20. data/lib/ruby_lsp/requests/code_actions.rb +30 -9
  21. data/lib/ruby_lsp/requests/diagnostics.rb +29 -77
  22. data/lib/ruby_lsp/requests/document_highlight.rb +111 -0
  23. data/lib/ruby_lsp/requests/document_symbol.rb +75 -16
  24. data/lib/ruby_lsp/requests/folding_ranges.rb +63 -19
  25. data/lib/ruby_lsp/requests/formatting.rb +19 -2
  26. data/lib/ruby_lsp/requests/rubocop_request.rb +21 -8
  27. data/lib/ruby_lsp/requests/selection_ranges.rb +114 -0
  28. data/lib/ruby_lsp/requests/semantic_highlighting.rb +132 -61
  29. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +100 -0
  30. data/lib/ruby_lsp/requests/support/selection_range.rb +20 -0
  31. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +70 -0
  32. data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +32 -0
  33. data/lib/ruby_lsp/requests.rb +10 -0
  34. data/lib/ruby_lsp/store.rb +23 -2
  35. data/rakelib/check_docs.rake +57 -0
  36. data/ruby-lsp.gemspec +2 -1
  37. data/sorbet/config +4 -0
  38. data/sorbet/rbi/.rubocop.yml +8 -0
  39. data/sorbet/rbi/gems/ansi@1.5.0.rbi +338 -0
  40. data/sorbet/rbi/gems/ast@2.4.2.rbi +522 -0
  41. data/sorbet/rbi/gems/builder@3.2.4.rbi +418 -0
  42. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  43. data/sorbet/rbi/gems/debug@1.5.0.rbi +1273 -0
  44. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +867 -0
  45. data/sorbet/rbi/gems/io-console@0.5.11.rbi +8 -0
  46. data/sorbet/rbi/gems/irb@1.4.1.rbi +376 -0
  47. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +7325 -0
  48. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  49. data/sorbet/rbi/gems/minitest-reporters@1.5.0.rbi +612 -0
  50. data/sorbet/rbi/gems/minitest@5.15.0.rbi +994 -0
  51. data/sorbet/rbi/gems/parallel@1.22.1.rbi +163 -0
  52. data/sorbet/rbi/gems/parser@3.1.2.0.rbi +3968 -0
  53. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +734 -0
  54. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  55. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +227 -0
  56. data/sorbet/rbi/gems/rake@13.0.6.rbi +1853 -0
  57. data/sorbet/rbi/gems/rbi@0.0.14.rbi +2337 -0
  58. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +1854 -0
  59. data/sorbet/rbi/gems/reline@0.3.1.rbi +1274 -0
  60. data/sorbet/rbi/gems/rexml@3.2.5.rbi +3852 -0
  61. data/sorbet/rbi/gems/rubocop-ast@1.18.0.rbi +4180 -0
  62. data/sorbet/rbi/gems/rubocop-minitest@0.20.0.rbi +1369 -0
  63. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +246 -0
  64. data/sorbet/rbi/gems/rubocop-shopify@2.6.0.rbi +8 -0
  65. data/sorbet/rbi/gems/rubocop-sorbet@0.6.8.rbi +652 -0
  66. data/sorbet/rbi/gems/rubocop@1.30.0.rbi +36729 -0
  67. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +732 -0
  68. data/sorbet/rbi/gems/spoom@1.1.11.rbi +1600 -0
  69. data/sorbet/rbi/gems/syntax_tree@2.7.1.rbi +6777 -0
  70. data/sorbet/rbi/gems/tapioca@0.8.1.rbi +1972 -0
  71. data/sorbet/rbi/gems/thor@1.2.1.rbi +2921 -0
  72. data/sorbet/rbi/gems/unicode-display_width@2.1.0.rbi +27 -0
  73. data/sorbet/rbi/gems/unparser@0.6.5.rbi +2789 -0
  74. data/sorbet/rbi/gems/webrick@1.7.0.rbi +1779 -0
  75. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +289 -0
  76. data/sorbet/rbi/gems/yard@0.9.27.rbi +13048 -0
  77. data/sorbet/rbi/shims/fiddle.rbi +4 -0
  78. data/sorbet/rbi/shims/hash.rbi +6 -0
  79. data/sorbet/rbi/shims/rdoc.rbi +4 -0
  80. data/sorbet/tapioca/config.yml +13 -0
  81. data/sorbet/tapioca/require.rb +7 -0
  82. metadata +74 -6
  83. data/shipit.production.yml +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25bad06533efa8072f0d501520e7429220be622d06a9061eb02171e1d515a498
4
- data.tar.gz: dc8cc9436d3b57b362661ba36b8f02b90495287386f94ac5b20916ce5e6025bf
3
+ metadata.gz: 2b072ffa3ea73a83aba6d6c32b931c4de30b0de06fd70655d620c3ed95bd8aa4
4
+ data.tar.gz: 24568688b20ffed38defcbc0b00708eb22a7dad20a9b5f397348242a64359bfd
5
5
  SHA512:
6
- metadata.gz: b8ae688533a36c018cfbca467f7db2b54d121787cff48b7fd56723c03790df34c99a2ace3a0770ee6e3971b5ac1aa5e559b9264d1466ba93556f3d9ec051abaa
7
- data.tar.gz: 1db946d571c62209b40fc4aef7048ea55f39c446bf87ff638d44aefcbdf2e8abdfad79c60ec5e9f0c3dc1fdda78f867582e4f58b56c861941197651bd286e6b3
6
+ metadata.gz: ea1830f0be08cb291e67d80bbe21dbe17f3e4b4d285b1d9112e004ca2feff32eaca85af755bc2635e3341ab4e64491710c3ba13c5bed617e34495534e1d7a5f8
7
+ data.tar.gz: f84b1b27c5c17455d1e8423f1e52f98a754ed123e1722bd536ee7bfdc754952e5d6619c0f4402664a33dc278bdc2422a3e7a37c8de5f172e4456e7a6c08b7a67
@@ -4,7 +4,7 @@ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  build:
7
- runs-on: self-hosted
7
+ runs-on: ubuntu-latest
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
@@ -17,7 +17,15 @@ jobs:
17
17
  with:
18
18
  ruby-version: ${{ matrix.ruby }}
19
19
  bundler-cache: true
20
+
21
+ - name: Check if documentation is up to date
22
+ run: bundle exec rake check_docs
23
+
24
+ - name: Typecheck
25
+ run: bundle exec srb tc
26
+
20
27
  - name: Lint Ruby files
21
28
  run: bin/rubocop
29
+
22
30
  - name: Run tests
23
31
  run: bin/test
@@ -0,0 +1,32 @@
1
+ name: Publish docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ name: Publish documentation website
11
+ steps:
12
+ - uses: actions/checkout@v3
13
+
14
+ - name: Set up Ruby
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: 3.1.1
18
+ bundler-cache: true
19
+
20
+ - name: Configure git
21
+ run: |
22
+ git config user.name github-actions
23
+ git config user.email github-actions@github.com
24
+
25
+ - name: Generate documentation
26
+ run: bundle exec rake yard
27
+
28
+ - name: Commit to gh-pages
29
+ run: |
30
+ git add docs
31
+ git commit -m "Publish website $(git log --format=format:%h -1)"
32
+ git push --force origin main:gh-pages
data/.rubocop.yml CHANGED
@@ -1,6 +1,11 @@
1
1
  inherit_gem:
2
2
  rubocop-shopify: rubocop.yml
3
3
 
4
+ require:
5
+ - rubocop-sorbet
6
+ - rubocop-minitest
7
+ - rubocop-rake
8
+
4
9
  AllCops:
5
10
  NewCops: disable
6
11
  SuggestExtensions: false
@@ -8,7 +13,27 @@ AllCops:
8
13
  Exclude:
9
14
  - "vendor/**/*"
10
15
  - "features/**/*"
16
+ - "test/fixtures/**/*"
11
17
 
12
18
  Naming/FileName:
13
19
  Exclude:
14
20
  - "lib/ruby-lsp.rb"
21
+
22
+ Sorbet/FalseSigil:
23
+ Enabled: false
24
+
25
+ Sorbet/TrueSigil:
26
+ Enabled: true
27
+ Include:
28
+ - "test/**/*.rb"
29
+ Exclude:
30
+ - "**/*.rake"
31
+ - "lib/**/*.rb"
32
+
33
+ Sorbet/StrictSigil:
34
+ Enabled: true
35
+ Include:
36
+ - "lib/**/*.rb"
37
+ Exclude:
38
+ - "**/*.rake"
39
+ - "test/**/*.rb"
data/CHANGELOG.md CHANGED
@@ -6,6 +6,29 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.0]
10
+
11
+ - Implement token modifiers in SemanticTokenEncoder ([#112](https://github.com/Shopify/ruby-lsp/pull/112))
12
+ - Add semantic token for name in a method definition ([#133](https://github.com/Shopify/ruby-lsp/pull/133))
13
+ - Add semantic highighting for def endless and singleton method names ([#134](https://github.com/Shopify/ruby-lsp/pull/134))
14
+ - Add semantic token for keyword self ([#137](https://github.com/Shopify/ruby-lsp/pull/137))
15
+ - Add semantic token for constants ([#138](https://github.com/Shopify/ruby-lsp/pull/138))
16
+ - Improve error handling + fix formatting hanging issue ([#149](https://github.com/Shopify/ruby-lsp/pull/149))
17
+ - Set the minimum syntax_tree version to 2.4 ([#151](https://github.com/Shopify/ruby-lsp/pull/151))
18
+
19
+ ## [0.0.4]
20
+
21
+ - Add basic document highlight (https://github.com/Shopify/ruby-lsp/pull/91)
22
+ - Add error telemetry (https://github.com/Shopify/ruby-lsp/pull/100)
23
+ - Always push telemetry events from the server (https://github.com/Shopify/ruby-lsp/pull/109)
24
+ - Fix multibyte character handling (https://github.com/Shopify/ruby-lsp/pull/122)
25
+ - Add Sorbet to the Ruby LSP (https://github.com/Shopify/ruby-lsp/pull/119, https://github.com/Shopify/ruby-lsp/pull/123)
26
+
27
+ ## [0.0.3]
28
+
29
+ - Fixed code actions return hanging
30
+ - Moved to incremental text synchronization
31
+ - Added syntax error resiliency
9
32
 
10
33
  ## [0.0.2]
11
34
 
data/Gemfile CHANGED
@@ -4,10 +4,14 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- gem "debug", "~> 1.5"
7
+ gem "debug", "~> 1.5", require: false
8
8
  gem "minitest", "~> 5.15"
9
9
  gem "minitest-reporters", "~> 1.5"
10
10
  gem "rake", "~> 13.0"
11
- gem "rubocop-shopify", "~> 2.5"
12
- gem "rubocop-minitest", "~> 0.19.1"
13
- gem "rubocop-rake", "~> 0.6.0"
11
+ gem "rubocop-shopify", "~> 2.7", require: false
12
+ gem "rubocop-minitest", "~> 0.20.1", require: false
13
+ gem "rubocop-rake", "~> 0.6.0", require: false
14
+ gem "rubocop-sorbet", "~> 0.6", require: false
15
+ gem "sorbet-static-and-runtime"
16
+ gem "tapioca", "~> 0.8", require: false
17
+ gem "yard", "~> 0.9", require: false
data/Gemfile.lock CHANGED
@@ -1,10 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-lsp (0.0.2)
4
+ ruby-lsp (0.1.0)
5
5
  language_server-protocol
6
6
  rubocop (>= 1.0)
7
- syntax_tree (>= 2.3)
7
+ sorbet-runtime
8
+ syntax_tree (>= 2.4)
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
@@ -12,13 +13,16 @@ GEM
12
13
  ansi (1.5.0)
13
14
  ast (2.4.2)
14
15
  builder (3.2.4)
16
+ coderay (1.1.3)
15
17
  debug (1.5.0)
16
18
  irb (>= 1.3.6)
17
19
  reline (>= 0.2.7)
20
+ diff-lcs (1.5.0)
18
21
  io-console (0.5.11)
19
22
  irb (1.4.1)
20
23
  reline (>= 0.3.0)
21
24
  language_server-protocol (3.16.0.3)
25
+ method_source (1.0.0)
22
26
  minitest (5.15.0)
23
27
  minitest-reporters (1.5.0)
24
28
  ansi
@@ -28,32 +32,75 @@ GEM
28
32
  parallel (1.22.1)
29
33
  parser (3.1.2.0)
30
34
  ast (~> 2.4.1)
35
+ prettier_print (0.1.0)
36
+ pry (0.14.1)
37
+ coderay (~> 1.1)
38
+ method_source (~> 1.0)
31
39
  rainbow (3.1.1)
32
40
  rake (13.0.6)
33
- regexp_parser (2.3.1)
41
+ rbi (0.0.14)
42
+ ast
43
+ parser (>= 2.6.4.0)
44
+ sorbet-runtime (>= 0.5.9204)
45
+ unparser
46
+ regexp_parser (2.5.0)
34
47
  reline (0.3.1)
35
48
  io-console (~> 0.5)
36
49
  rexml (3.2.5)
37
- rubocop (1.28.1)
50
+ rubocop (1.30.1)
38
51
  parallel (~> 1.10)
39
52
  parser (>= 3.1.0.0)
40
53
  rainbow (>= 2.2.2, < 4.0)
41
54
  regexp_parser (>= 1.8, < 3.0)
42
- rexml
43
- rubocop-ast (>= 1.17.0, < 2.0)
55
+ rexml (>= 3.2.5, < 4.0)
56
+ rubocop-ast (>= 1.18.0, < 2.0)
44
57
  ruby-progressbar (~> 1.7)
45
58
  unicode-display_width (>= 1.4.0, < 3.0)
46
- rubocop-ast (1.17.0)
59
+ rubocop-ast (1.18.0)
47
60
  parser (>= 3.1.1.0)
48
- rubocop-minitest (0.19.1)
61
+ rubocop-minitest (0.20.1)
49
62
  rubocop (>= 0.90, < 2.0)
50
63
  rubocop-rake (0.6.0)
51
64
  rubocop (~> 1.0)
52
- rubocop-shopify (2.5.0)
53
- rubocop (~> 1.25)
65
+ rubocop-shopify (2.7.0)
66
+ rubocop (~> 1.30)
67
+ rubocop-sorbet (0.6.8)
68
+ rubocop (>= 0.90.0)
54
69
  ruby-progressbar (1.11.0)
55
- syntax_tree (2.3.1)
70
+ sorbet (0.5.10092)
71
+ sorbet-static (= 0.5.10092)
72
+ sorbet-runtime (0.5.10092)
73
+ sorbet-static (0.5.10092-universal-darwin-21)
74
+ sorbet-static (0.5.10092-x86_64-linux)
75
+ sorbet-static-and-runtime (0.5.10092)
76
+ sorbet (= 0.5.10092)
77
+ sorbet-runtime (= 0.5.10092)
78
+ spoom (1.1.11)
79
+ sorbet (>= 0.5.9204)
80
+ sorbet-runtime (>= 0.5.9204)
81
+ thor (>= 0.19.2)
82
+ syntax_tree (2.7.1)
83
+ prettier_print
84
+ tapioca (0.8.1)
85
+ bundler (>= 1.17.3)
86
+ parallel (>= 1.21.0)
87
+ pry (>= 0.12.2)
88
+ rbi (~> 0.0.0, >= 0.0.14)
89
+ sorbet-static-and-runtime (>= 0.5.9204)
90
+ spoom (~> 1.1.0, >= 1.1.11)
91
+ thor (>= 1.2.0)
92
+ yard-sorbet
93
+ thor (1.2.1)
56
94
  unicode-display_width (2.1.0)
95
+ unparser (0.6.5)
96
+ diff-lcs (~> 1.3)
97
+ parser (>= 3.1.0)
98
+ webrick (1.7.0)
99
+ yard (0.9.28)
100
+ webrick (~> 1.7.0)
101
+ yard-sorbet (0.6.1)
102
+ sorbet-runtime (>= 0.5)
103
+ yard (>= 0.9)
57
104
 
58
105
  PLATFORMS
59
106
  arm64-darwin-21
@@ -64,10 +111,14 @@ DEPENDENCIES
64
111
  minitest (~> 5.15)
65
112
  minitest-reporters (~> 1.5)
66
113
  rake (~> 13.0)
67
- rubocop-minitest (~> 0.19.1)
114
+ rubocop-minitest (~> 0.20.1)
68
115
  rubocop-rake (~> 0.6.0)
69
- rubocop-shopify (~> 2.5)
116
+ rubocop-shopify (~> 2.7)
117
+ rubocop-sorbet (~> 0.6)
70
118
  ruby-lsp!
119
+ sorbet-static-and-runtime
120
+ tapioca (~> 0.8)
121
+ yard (~> 0.9)
71
122
 
72
123
  BUNDLED WITH
73
124
  2.3.7
data/README.md CHANGED
@@ -1,4 +1,23 @@
1
- # ruby-lsp
1
+ ![Build Status](https://github.com/Shopify/ruby-lsp/workflows/CI/badge.svg)
2
+
3
+ # Ruby LSP
4
+
5
+ This gem is an implementation of the language server protocol specification for Ruby, used to improve editor features.
6
+
7
+ ## Usage
8
+
9
+ Install the gem. There's no need to require it, since the server is used as a standalone executable.
10
+
11
+ ```ruby
12
+ group :development do
13
+ gem "ruby-lsp", require: false
14
+ end
15
+ ```
16
+
17
+ If using VS Code, install the [Ruby LSP plugin](https://github.com/Shopify/vscode-ruby-lsp) to get the extra features in
18
+ the editor.
19
+
20
+ See the [documentation](https://shopify.github.io/ruby-lsp) for supported features.
2
21
 
3
22
  ## Contributing
4
23
 
@@ -8,6 +27,44 @@ are expected to adhere to the
8
27
  [Contributor Covenant](https://github.com/Shopify/ruby-lsp/blob/main/CODE_OF_CONDUCT.md)
9
28
  code of conduct.
10
29
 
30
+ ### Expectation testing
31
+
32
+ To simplify the way we run tests over different pieces of Ruby code, we use a custom expectations test framework against a set of Ruby fixtures.
33
+
34
+ To add a new fixture to the expectations test suite:
35
+
36
+ 1. Add a new fixture `my_fixture.rb` file under `test/fixtures`
37
+ 2. (optional) Add new expectations under `test/expectations/$HANDLER` for the request handlers you're concerned by
38
+ 3. Profit by running `bin/test test/requests/$HANDLER_expectations_test my_fixture`
39
+ * Handlers for which you added expectations will be checked with `assert_expectations`
40
+ * Handlers without expectations will be ran against your new test to check that nothing breaks
41
+
42
+ To add a new expectations test runner for a new request handler:
43
+
44
+ 1. Add a new file under `test/requests/$HANDLER_expectations_test.rb` that subclasses `ExpectationsTestRunner` and calls `expectations_tests $HANDLER, "$EXPECTATIONS_DIR"` where: `$HANDLER` is the fully qualified name or your handler class and `$EXPECTATIONS_DIR` is the directory name where you want to store the expectation files.
45
+
46
+ ```rb
47
+ # frozen_string_literal: true
48
+
49
+ require "test_helper"
50
+ require "expectations/expectations_test_runner"
51
+
52
+ class $HANDLERExpectationsTest < ExpectationsTestRunner
53
+ expectations_tests RubyLsp::Requests::$HANDLER, "$EXPECTATIONS_DIR"
54
+ end
55
+ ```
56
+
57
+ 2. (optional) Override the `run_expectations` and `assert_expectations` methods if needed. See the different request handler expectations runners under `test/requests/*_expectations_test.rb` for examples.
58
+
59
+ 4. (optional) Add new fixtures for your handler under `test/fixtures`
60
+
61
+ 5. (optional) Add new expectations under `test/expectations/$HANDLER`
62
+ * No need to write the expectations by hand, just run the test with an empty expectation file and copy from the output.
63
+
64
+ 7. Profit by running, `bin/test test/expectations_test $HANDLER`
65
+ * Tests with expectations will be checked with `assert_expectations`
66
+ * Tests without expectations will be ran against your new $HANDLER to check that nothing breaks
67
+
11
68
  ## Debugging
12
69
 
13
70
  ### Tracing LSP requests and responses
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
+ require "yard"
5
6
 
6
7
  Rake::TestTask.new(:test) do |t|
7
8
  t.libs << "test"
@@ -9,6 +10,10 @@ Rake::TestTask.new(:test) do |t|
9
10
  t.test_files = FileList["test/**/*_test.rb"]
10
11
  end
11
12
 
13
+ YARD::Rake::YardocTask.new do |t|
14
+ t.options = ["--markup", "markdown", "--output-dir", "docs"]
15
+ end
16
+
12
17
  require "rubocop/rake_task"
13
18
 
14
19
  RuboCop::RakeTask.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.1.0
data/bin/tapioca ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tapioca' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("tapioca", "tapioca")
data/dev.yml CHANGED
@@ -10,6 +10,9 @@ up:
10
10
  commands:
11
11
  server: exe/ruby-lsp
12
12
  style: bin/rubocop
13
+ typecheck:
14
+ aliases: ["tc"]
15
+ run: "bundle exec srb tc"
13
16
  test:
14
17
  syntax:
15
18
  argument: file
data/exe/ruby-lsp CHANGED
@@ -1,7 +1,23 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "syntax_tree"
5
- require "ruby_lsp/cli"
4
+ require "sorbet-runtime"
6
5
 
7
- RubyLsp::Cli.start(ARGV)
6
+ begin
7
+ T::Configuration.default_checked_level = :never
8
+ # Suppresses call validation errors
9
+ T::Configuration.call_validation_error_handler = ->(*) {}
10
+ # Suppresses errors caused by T.cast, T.let, T.must, etc.
11
+ T::Configuration.inline_type_error_handler = ->(*) {}
12
+ # Suppresses errors caused by incorrect parameter ordering
13
+ T::Configuration.sig_validation_error_handler = ->(*) {}
14
+ rescue
15
+ # Need this rescue so that if another gem has
16
+ # already set the checked level by the time we
17
+ # get to it, we don't fail outright.
18
+ nil
19
+ end
20
+
21
+ require_relative "../lib/ruby_lsp/internal"
22
+
23
+ RubyLsp::Cli.start
data/lib/ruby-lsp.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module RubyLsp
5
+ VERSION = T.let(File.read(File.expand_path("../VERSION", __dir__)).strip, String)
4
6
  end
data/lib/ruby_lsp/cli.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require "language_server-protocol"
@@ -6,22 +7,26 @@ require_relative "handler"
6
7
 
7
8
  module RubyLsp
8
9
  module Cli
9
- def self.start(_argv)
10
+ extend T::Sig
11
+
12
+ sig { void }
13
+ def self.start
10
14
  handler = RubyLsp::Handler.new
11
15
 
12
16
  handler.config do
13
17
  on("initialize") do |request|
14
18
  store.clear
15
- respond_with_capabilities(request.dig(:params, :initializationOptions).fetch(:enabledFeatures, []))
19
+ initialization_options = request.dig(:params, :initializationOptions)
20
+
21
+ respond_with_capabilities(initialization_options.fetch(:enabledFeatures, []))
16
22
  end
17
23
 
18
24
  on("textDocument/didChange") do |request|
19
25
  uri = request.dig(:params, :textDocument, :uri)
20
- text = request.dig(:params, :contentChanges, 0, :text)
21
- store.set(uri, text)
26
+ store.push_edits(uri, request.dig(:params, :contentChanges))
22
27
 
23
28
  send_diagnostics(uri)
24
- nil
29
+ RubyLsp::Handler::VOID
25
30
  end
26
31
 
27
32
  on("textDocument/didOpen") do |request|
@@ -30,14 +35,14 @@ module RubyLsp
30
35
  store.set(uri, text)
31
36
 
32
37
  send_diagnostics(uri)
33
- nil
38
+ RubyLsp::Handler::VOID
34
39
  end
35
40
 
36
41
  on("textDocument/didClose") do |request|
37
42
  uri = request.dig(:params, :textDocument, :uri)
38
43
  store.delete(uri)
39
44
 
40
- nil
45
+ RubyLsp::Handler::VOID
41
46
  end
42
47
 
43
48
  on("textDocument/documentSymbol") do |request|
@@ -48,6 +53,13 @@ module RubyLsp
48
53
  respond_with_folding_ranges(request.dig(:params, :textDocument, :uri))
49
54
  end
50
55
 
56
+ on("textDocument/selectionRange") do |request|
57
+ respond_with_selection_ranges(
58
+ request.dig(:params, :textDocument, :uri),
59
+ request.dig(:params, :positions),
60
+ )
61
+ end
62
+
51
63
  on("textDocument/semanticTokens/full") do |request|
52
64
  respond_with_semantic_highlighting(request.dig(:params, :textDocument, :uri))
53
65
  end
@@ -56,6 +68,10 @@ module RubyLsp
56
68
  respond_with_formatting(request.dig(:params, :textDocument, :uri))
57
69
  end
58
70
 
71
+ on("textDocument/documentHighlight") do |request|
72
+ respond_with_document_highlight(request.dig(:params, :textDocument, :uri), request.dig(:params, :position))
73
+ end
74
+
59
75
  on("textDocument/codeAction") do |request|
60
76
  range = request.dig(:params, :range)
61
77
  start_line = range.dig(:start, :line)
@@ -1,27 +1,119 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module RubyLsp
4
5
  class Document
5
- attr_reader :tree, :parser, :source
6
+ extend T::Sig
6
7
 
8
+ PositionShape = T.type_alias { { line: Integer, character: Integer } }
9
+ RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
10
+ EditShape = T.type_alias { { range: RangeShape, text: String } }
11
+
12
+ sig { returns(SyntaxTree::Node) }
13
+ attr_reader :tree
14
+
15
+ sig { returns(String) }
16
+ attr_reader :source
17
+
18
+ sig { returns(T::Array[EditShape]) }
19
+ attr_reader :syntax_error_edits
20
+
21
+ sig { params(source: String).void }
7
22
  def initialize(source)
23
+ @tree = T.let(SyntaxTree.parse(source), SyntaxTree::Node)
24
+ @cache = T.let({}, T::Hash[Symbol, T.untyped])
25
+ @syntax_error_edits = T.let([], T::Array[EditShape])
8
26
  @source = source
9
- @parser = SyntaxTree::Parser.new(source)
10
- @tree = @parser.parse
11
- @cache = {}
27
+ @parsable_source = T.let(source.dup, String)
12
28
  end
13
29
 
30
+ sig { params(other: Document).returns(T::Boolean) }
14
31
  def ==(other)
15
32
  @source == other.source
16
33
  end
17
34
 
18
- def cache_fetch(request_name)
35
+ sig do
36
+ type_parameters(:T)
37
+ .params(
38
+ request_name: Symbol,
39
+ block: T.proc.params(document: Document).returns(T.type_parameter(:T))
40
+ ).returns(T.type_parameter(:T))
41
+ end
42
+ def cache_fetch(request_name, &block)
19
43
  cached = @cache[request_name]
20
44
  return cached if cached
21
45
 
22
- result = yield(self)
46
+ result = block.call(self)
23
47
  @cache[request_name] = result
24
48
  result
25
49
  end
50
+
51
+ sig { params(edits: T::Array[EditShape]).void }
52
+ def push_edits(edits)
53
+ # Apply the edits on the real source
54
+ edits.each { |edit| apply_edit(@source, edit[:range], edit[:text]) }
55
+
56
+ @cache.clear
57
+ @tree = SyntaxTree.parse(@source)
58
+ @syntax_error_edits.clear
59
+ @parsable_source = @source.dup
60
+ nil
61
+ rescue SyntaxTree::Parser::ParseError
62
+ update_parsable_source(edits)
63
+ end
64
+
65
+ sig { returns(T::Boolean) }
66
+ def syntax_errors?
67
+ @syntax_error_edits.any?
68
+ end
69
+
70
+ private
71
+
72
+ sig { params(edits: T::Array[EditShape]).void }
73
+ def update_parsable_source(edits)
74
+ # If the new edits caused a syntax error, make all edits blank spaces and line breaks to adjust the line and
75
+ # column numbers. This is attempt to make the document parsable while partial edits are being applied
76
+ edits.each do |edit|
77
+ @syntax_error_edits << edit
78
+ next if edit[:text].empty? # skip deletions, since they may have caused the syntax error
79
+
80
+ apply_edit(@parsable_source, edit[:range], edit[:text].gsub(/[^\r\n]/, " "))
81
+ end
82
+
83
+ @tree = SyntaxTree.parse(@parsable_source)
84
+ rescue SyntaxTree::Parser::ParseError
85
+ # If we can't parse the source even after emptying the edits, then just fallback to the previous source
86
+ end
87
+
88
+ sig { params(source: String, range: RangeShape, text: String).void }
89
+ def apply_edit(source, range, text)
90
+ scanner = Scanner.new(source)
91
+ start_position = scanner.find_position(range[:start])
92
+ end_position = scanner.find_position(range[:end])
93
+
94
+ source[start_position...end_position] = text
95
+ end
96
+
97
+ class Scanner
98
+ extend T::Sig
99
+
100
+ sig { params(source: String).void }
101
+ def initialize(source)
102
+ @current_line = T.let(0, Integer)
103
+ @pos = T.let(0, Integer)
104
+ @source = source
105
+ end
106
+
107
+ sig { params(position: PositionShape).returns(Integer) }
108
+ def find_position(position)
109
+ until @current_line == position[:line]
110
+ @pos += 1 until /\R/.match?(@source[@pos])
111
+ @pos += 1
112
+ @current_line += 1
113
+ end
114
+
115
+ @pos + position[:character]
116
+ end
117
+ end
26
118
  end
27
119
  end