ruby-lsp 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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