rucoa 0.2.0 → 0.3.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +4 -2
  5. data/lib/rucoa/cli.rb +2 -2
  6. data/lib/rucoa/configuration.rb +63 -0
  7. data/lib/rucoa/diagnostic_provider.rb +12 -28
  8. data/lib/rucoa/formatting_provider.rb +2 -0
  9. data/lib/rucoa/handler_concerns/configuration_requestable.rb +35 -0
  10. data/lib/rucoa/handler_concerns/diagnostics_publishable.rb +32 -0
  11. data/lib/rucoa/handler_concerns.rb +8 -0
  12. data/lib/rucoa/handlers/base.rb +61 -0
  13. data/lib/rucoa/handlers/initialize_handler.rb +22 -0
  14. data/lib/rucoa/handlers/initialized_handler.rb +13 -0
  15. data/lib/rucoa/handlers/text_document_code_action_handler.rb +29 -0
  16. data/lib/rucoa/handlers/text_document_did_change_handler.rb +31 -0
  17. data/lib/rucoa/handlers/text_document_did_open_handler.rb +31 -0
  18. data/lib/rucoa/handlers/text_document_formatting_handler.rb +33 -0
  19. data/lib/rucoa/handlers/text_document_range_formatting_handler.rb +24 -0
  20. data/lib/rucoa/handlers/text_document_selection_range_handler.rb +25 -0
  21. data/lib/rucoa/handlers/workspace_did_change_configuration_handler.rb +13 -0
  22. data/lib/rucoa/handlers.rb +16 -0
  23. data/lib/rucoa/message_reader.rb +3 -1
  24. data/lib/rucoa/message_writer.rb +3 -0
  25. data/lib/rucoa/range.rb +35 -9
  26. data/lib/rucoa/range_formatting_provider.rb +67 -0
  27. data/lib/rucoa/rubocop_configuration_checker.rb +42 -0
  28. data/lib/rucoa/selection_range_provider.rb +3 -1
  29. data/lib/rucoa/server.rb +79 -124
  30. data/lib/rucoa/source_store.rb +7 -1
  31. data/lib/rucoa/version.rb +1 -1
  32. data/lib/rucoa.rb +5 -0
  33. metadata +19 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76603729adc41973a6692fa067867eefa24f1724afa9d972f224dc34338cf925
4
- data.tar.gz: d5c4a1c8001121bafbb9a768f8c5d91d6ae66a737e8b415572756772450d9ba1
3
+ metadata.gz: 7206c3901e7ceea282b2de34f31ab90cc5ffa7fa48c06e58ae64bb718f48cd52
4
+ data.tar.gz: 6188a2047317b98160565599de3dc08663e732d7b54a546a8bc3e610379cb39a
5
5
  SHA512:
6
- metadata.gz: de17db3774a60396fa40a307cf1d34c73db06b55afa9a3e2e2cae2085f1aeef07e459ace8149cf668e0cee47ca940a29066c963e599633e6c647dee31c36b1a0
7
- data.tar.gz: c309425209a6146a1ea4031f57b752bcef0ce7fe25f965704d6a334194000e8aa96a94aaad992bed04ddc200397ee9790349e0d5e94998703eaeafa4fd8f3558
6
+ metadata.gz: afa9c37dae4a5c460eb1f5505ca511138c20f5b8c483a5ba68f798d1f9b18a5f5c4a481ee3ec743eb8585328538b5daec613b9f69a9599e5337106c57b1c0ea8
7
+ data.tar.gz: 539517254cff781a99b6ec463aed13bd04cbbcf3da3a9ff15cf72dac809b12c53ef9ff53c51828a163975bbdbe60a174767a472d2b4476f03f1813b5f513c62d
data/.rubocop.yml CHANGED
@@ -20,6 +20,9 @@ RSpec/ExampleLength:
20
20
  RSpec/ImplicitSubject:
21
21
  Enabled: false
22
22
 
23
+ RSpec/MultipleExpectations:
24
+ Enabled: false
25
+
23
26
  RSpec/MultipleMemoizedHelpers:
24
27
  Enabled: false
25
28
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rucoa (0.2.0)
4
+ rucoa (0.3.0)
5
5
  parser
6
6
  rubocop
7
7
 
data/README.md CHANGED
@@ -35,11 +35,13 @@ gem install rucoa
35
35
  - Diagnostics
36
36
  - Formatting
37
37
  - Quick Fix
38
- - Selection Ranges (experimental)
38
+ - Selection Range
39
39
 
40
40
  ### Coming soon
41
41
 
42
42
  - Completion
43
43
  - Documentation
44
- - Highlight
45
44
  - Go to Definition
45
+ - Highlight
46
+ - Semantic Tokens
47
+ - Symbol
data/lib/rucoa/cli.rb CHANGED
@@ -16,8 +16,8 @@ module Rucoa
16
16
  # @return [void]
17
17
  def call
18
18
  Server.new(
19
- reader: $stdin,
20
- writer: $stdout
19
+ input: $stdin,
20
+ output: $stdout
21
21
  ).start
22
22
  end
23
23
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ class Configuration
5
+ DEFAULT_SETTINGS = {
6
+ 'base' => {
7
+ 'enable' => 'auto',
8
+ 'useBundler' => 'auto'
9
+ },
10
+ 'feature' => {
11
+ 'codeAction' => {
12
+ 'enable' => true
13
+ },
14
+ 'diagnostics' => {
15
+ 'enable' => true
16
+ },
17
+ 'formatting' => {
18
+ 'enable' => true
19
+ },
20
+ 'selectionRange' => {
21
+ 'enable' => true
22
+ }
23
+ }
24
+ }.freeze
25
+
26
+ def initialize
27
+ reset
28
+ end
29
+
30
+ # @return [Boolean]
31
+ def enables_code_action?
32
+ @settings.dig('feature', 'codeAction', 'enable')
33
+ end
34
+
35
+ # @return [Boolean]
36
+ def enables_diagnostics?
37
+ @settings.dig('feature', 'diagnostics', 'enable')
38
+ end
39
+
40
+ # @return [Boolean]
41
+ def enables_formatting?
42
+ @settings.dig('feature', 'formatting', 'enable')
43
+ end
44
+
45
+ # @return [Boolean]
46
+ def enables_selection_range?
47
+ @settings.dig('feature', 'selectionRange', 'enable')
48
+ end
49
+
50
+ # @param settings [Hash]
51
+ # @return [void]
52
+ def update(settings)
53
+ @settings = settings
54
+ end
55
+
56
+ private
57
+
58
+ # @return [void]
59
+ def reset
60
+ @settings = DEFAULT_SETTINGS.dup
61
+ end
62
+ end
63
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pathname'
4
-
5
3
  module Rucoa
6
4
  class DiagnosticProvider
7
5
  # @param source [Rucoa::Source]
@@ -23,11 +21,12 @@ module Rucoa
23
21
 
24
22
  # @return [Array<Hash>]
25
23
  def call
26
- return [] unless rubocop_configured?
24
+ return [] unless RubocopConfigurationChecker.call
27
25
 
28
26
  offenses.map do |offense|
29
27
  OffenseToDiagnosticMapper.call(
30
28
  offense,
29
+ source: @source,
31
30
  uri: @uri
32
31
  )
33
32
  end
@@ -40,26 +39,6 @@ module Rucoa
40
39
  RubocopInvestigator.call(source: @source)
41
40
  end
42
41
 
43
- # @return [Boolean]
44
- def rubocop_configured?
45
- each_ancestor_pathname.any? do |pathname|
46
- pathname.join('.rubocop.yml').exist?
47
- end
48
- end
49
-
50
- # @return [Enumerable<Pathname>]
51
- def each_ancestor_pathname
52
- return to_enum(__method__) unless block_given?
53
-
54
- pathname = ::Pathname.new(@source.path)
55
- loop do
56
- pathname = pathname.parent
57
- yield pathname
58
- break if pathname.root?
59
- end
60
- self
61
- end
62
-
63
42
  class OffenseToDiagnosticMapper
64
43
  LSP_SEVERITY_NAME_TO_VALUE_MAP = {
65
44
  error: 1,
@@ -81,17 +60,24 @@ module Rucoa
81
60
 
82
61
  class << self
83
62
  # @param offense [RuboCop::Cop::Offense]
63
+ # @param source [Rucoa::Source]
84
64
  # @param uri [String]
85
65
  # @return [Hash]
86
- def call(offense, uri:)
87
- new(offense, uri: uri).call
66
+ def call(offense, source:, uri:)
67
+ new(
68
+ offense,
69
+ source: source,
70
+ uri: uri
71
+ ).call
88
72
  end
89
73
  end
90
74
 
91
75
  # @param offense [RuboCop::Cop::Offense]
76
+ # @param source [Rucoa::Source]
92
77
  # @param uri [String]
93
- def initialize(offense, uri:)
78
+ def initialize(offense, source:, uri:)
94
79
  @offense = offense
80
+ @source = source
95
81
  @uri = uri
96
82
  end
97
83
 
@@ -119,8 +105,6 @@ module Rucoa
119
105
  {
120
106
  cop_name: @offense.cop_name,
121
107
  edits: edits,
122
- path: @offense.location.source_buffer.name,
123
- range: range,
124
108
  uri: @uri
125
109
  }
126
110
  end
@@ -17,6 +17,8 @@ module Rucoa
17
17
 
18
18
  # @return [Array<Hash>]
19
19
  def call
20
+ return [] unless RubocopConfigurationChecker.call
21
+
20
22
  [text_edit]
21
23
  end
22
24
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module HandlerConcerns
5
+ module ConfigurationRequestable
6
+ include HandlerConcerns::DiagnosticsPublishable
7
+
8
+ private
9
+
10
+ # @return [void]
11
+ def request_workspace_configuration
12
+ write(
13
+ method: 'workspace/configuration',
14
+ params: {
15
+ items: [
16
+ {
17
+ section: 'rucoa'
18
+ }
19
+ ]
20
+ }
21
+ ) do |response|
22
+ configuration.update(response['result'][0])
23
+ publish_diagnostics_on_each_source
24
+ end
25
+ end
26
+
27
+ # @return [void]
28
+ def publish_diagnostics_on_each_source
29
+ source_store.each_uri do |uri|
30
+ publish_diagnostics_on(uri)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module HandlerConcerns
5
+ module DiagnosticsPublishable
6
+ private
7
+
8
+ # @param uri [String]
9
+ # @return [Array<Hash>]
10
+ def diagnostics_on(uri)
11
+ return [] unless configuration.enables_diagnostics?
12
+
13
+ DiagnosticProvider.call(
14
+ source: source_store.get(uri),
15
+ uri: uri
16
+ )
17
+ end
18
+
19
+ # @param uri [String]
20
+ # @return [void]
21
+ def publish_diagnostics_on(uri)
22
+ write(
23
+ method: 'textDocument/publishDiagnostics',
24
+ params: {
25
+ diagnostics: diagnostics_on(uri),
26
+ uri: uri
27
+ }
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module HandlerConcerns
5
+ autoload :ConfigurationRequestable, 'rucoa/handler_concerns/configuration_requestable'
6
+ autoload :DiagnosticsPublishable, 'rucoa/handler_concerns/diagnostics_publishable'
7
+ end
8
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class Base
6
+ class << self
7
+ # @param server [Rucoa::Server]
8
+ # @param request [Hash]
9
+ # @return [void]
10
+ def call(request:, server:)
11
+ new(
12
+ request: request,
13
+ server: server
14
+ ).call
15
+ end
16
+ end
17
+
18
+ # @param request [Hash]
19
+ # @param server [Rucoa::Server]
20
+ def initialize(request:, server:)
21
+ @request = request
22
+ @server = server
23
+ end
24
+
25
+ # @return [void]
26
+ def call
27
+ raise ::NotImplementedError
28
+ end
29
+
30
+ private
31
+
32
+ # @return [Hash]
33
+ attr_reader :request
34
+
35
+ # @param message [Hash]
36
+ # @return [void]
37
+ def respond(message)
38
+ write(
39
+ id: request['id'],
40
+ result: message
41
+ )
42
+ end
43
+
44
+ # @return [Rucoa::Configuration]
45
+ def configuration
46
+ @server.configuration
47
+ end
48
+
49
+ # @return [Rucoa::SourceStore]
50
+ def source_store
51
+ @server.source_store
52
+ end
53
+
54
+ # @param message [Hash]
55
+ # @return [void]
56
+ def write(message, &block)
57
+ @server.write(message, &block)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class InitializeHandler < Base
6
+ def call
7
+ respond(
8
+ capabilities: {
9
+ codeActionProvider: true,
10
+ documentFormattingProvider: true,
11
+ documentRangeFormattingProvider: true,
12
+ selectionRangeProvider: true,
13
+ textDocumentSync: {
14
+ change: 1, # Full
15
+ openClose: true
16
+ }
17
+ }
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class InitializedHandler < Base
6
+ include HandlerConcerns::ConfigurationRequestable
7
+
8
+ def call
9
+ request_workspace_configuration
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentCodeActionHandler < Base
6
+ def call
7
+ return unless diagnostics
8
+
9
+ respond(code_actions)
10
+ end
11
+
12
+ private
13
+
14
+ # @return [Array<Hash>]
15
+ def code_actions
16
+ return [] unless configuration.enables_code_action?
17
+
18
+ CodeActionProvider.call(
19
+ diagnostics: diagnostics
20
+ )
21
+ end
22
+
23
+ # @return [Array<Hash>, nil]
24
+ def diagnostics
25
+ @diagnostics ||= request.dig('params', 'context', 'diagnostics')
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentDidChangeHandler < Base
6
+ include HandlerConcerns::DiagnosticsPublishable
7
+
8
+ def call
9
+ update_source
10
+ publish_diagnostics_on(uri)
11
+ end
12
+
13
+ private
14
+
15
+ # @return [void]
16
+ def update_source
17
+ source_store.set(uri, text)
18
+ end
19
+
20
+ # @return [String]
21
+ def text
22
+ request.dig('params', 'contentChanges')[0]['text']
23
+ end
24
+
25
+ # @return [String]
26
+ def uri
27
+ @uri ||= request.dig('params', 'textDocument', 'uri')
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentDidOpenHandler < Base
6
+ include HandlerConcerns::DiagnosticsPublishable
7
+
8
+ def call
9
+ update_source
10
+ publish_diagnostics_on(uri)
11
+ end
12
+
13
+ private
14
+
15
+ # @return [void]
16
+ def update_source
17
+ source_store.set(uri, text)
18
+ end
19
+
20
+ # @return [String]
21
+ def text
22
+ request.dig('params', 'textDocument', 'text')
23
+ end
24
+
25
+ # @return [String]
26
+ def uri
27
+ @uri ||= request.dig('params', 'textDocument', 'uri')
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentFormattingHandler < Base
6
+ def call
7
+ respond(edits)
8
+ end
9
+
10
+ private
11
+
12
+ # @return [Array<Hash>]
13
+ def edits
14
+ return unless configuration.enables_formatting?
15
+ return unless source
16
+
17
+ FormattingProvider.call(
18
+ source: source
19
+ )
20
+ end
21
+
22
+ # @return [Rucoa::Source, nil]
23
+ def source
24
+ @source ||= source_store.get(uri)
25
+ end
26
+
27
+ # @return [String]
28
+ def uri
29
+ request.dig('params', 'textDocument', 'uri')
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentRangeFormattingHandler < Base
6
+ def call
7
+ return unless configuration.enables_formatting?
8
+
9
+ uri = request.dig('params', 'textDocument', 'uri')
10
+ source = source_store.get(uri)
11
+ return unless source
12
+
13
+ respond(
14
+ RangeFormattingProvider.call(
15
+ range: Range.from_vscode_range(
16
+ request.dig('params', 'range')
17
+ ),
18
+ source: source
19
+ )
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentSelectionRangeHandler < Base
6
+ def call
7
+ return unless configuration.enables_selection_range?
8
+
9
+ source = source_store.get(
10
+ request.dig('params', 'textDocument', 'uri')
11
+ )
12
+ return unless source
13
+
14
+ respond(
15
+ request.dig('params', 'positions').filter_map do |position|
16
+ SelectionRangeProvider.call(
17
+ position: Position.from_vscode_position(position),
18
+ source: source
19
+ )
20
+ end
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class WorkspaceDidChangeConfigurationHandler < Base
6
+ include HandlerConcerns::ConfigurationRequestable
7
+
8
+ def call
9
+ request_workspace_configuration
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ autoload :Base, 'rucoa/handlers/base'
6
+ autoload :InitializeHandler, 'rucoa/handlers/initialize_handler'
7
+ autoload :InitializedHandler, 'rucoa/handlers/initialized_handler'
8
+ autoload :TextDocumentCodeActionHandler, 'rucoa/handlers/text_document_code_action_handler'
9
+ autoload :TextDocumentDidChangeHandler, 'rucoa/handlers/text_document_did_change_handler'
10
+ autoload :TextDocumentDidOpenHandler, 'rucoa/handlers/text_document_did_open_handler'
11
+ autoload :TextDocumentFormattingHandler, 'rucoa/handlers/text_document_formatting_handler'
12
+ autoload :TextDocumentRangeFormattingHandler, 'rucoa/handlers/text_document_range_formatting_handler'
13
+ autoload :TextDocumentSelectionRangeHandler, 'rucoa/handlers/text_document_selection_range_handler'
14
+ autoload :WorkspaceDidChangeConfigurationHandler, 'rucoa/handlers/workspace_did_change_configuration_handler'
15
+ end
16
+ end
@@ -11,8 +11,10 @@ module Rucoa
11
11
  end
12
12
 
13
13
  # @yieldparam message [Hash]
14
- # @return [void]
14
+ # @return [Enumerator<Hash>, void]
15
15
  def read
16
+ return enum_for(:read) unless block_given?
17
+
16
18
  while (buffer = @io.gets("\r\n\r\n"))
17
19
  content_length = buffer[/Content-Length: (\d+)/i, 1]
18
20
  raise Errors::ContentLengthHeaderNotFound unless content_length
@@ -19,6 +19,9 @@ module Rucoa
19
19
  end
20
20
  end
21
21
 
22
+ # @return [IO]
23
+ attr_reader :io
24
+
22
25
  # @param io [IO]
23
26
  def initialize(io)
24
27
  @io = io
data/lib/rucoa/range.rb CHANGED
@@ -11,19 +11,49 @@ module Rucoa
11
11
  Position.from_parser_range_ending(range)
12
12
  )
13
13
  end
14
+
15
+ # @param hash [Hash]
16
+ # @return [Rucoa::Range]
17
+ def from_vscode_range(hash)
18
+ new(
19
+ Position.from_vscode_position(hash['start']),
20
+ Position.from_vscode_position(hash['end'])
21
+ )
22
+ end
14
23
  end
15
24
 
25
+ # @return [Rucoa::Position]
26
+ attr_reader :beginning
27
+
28
+ # @return [Rucoa::Position]
29
+ attr_reader :ending
30
+
16
31
  # @param beginning [Rucoa::Position]
17
32
  # @param ending [Ruoca::Position]
18
- def initialize(beginning, ending)
33
+ # @param exclude_end [Boolean]
34
+ def initialize(beginning, ending, exclude_end: true)
19
35
  @beginning = beginning
20
36
  @ending = ending
37
+ @exclude_end = exclude_end
38
+ end
39
+
40
+ # @param range [Rucoa::Range]
41
+ # @return [Boolean]
42
+ def contains?(range)
43
+ copy = with_including_end
44
+ copy.include?(range.beginning) && copy.include?(range.ending)
21
45
  end
22
46
 
23
47
  # @param position [Rucoa::Position]
24
48
  # @return [Boolean]
25
49
  def include?(position)
26
- !exclude?(position)
50
+ return false if position.line > @ending.line
51
+ return false if position.line < @beginning.line
52
+ return false if position.column < @beginning.column
53
+ return false if position.column > @ending.column
54
+ return false if position.column == @ending.column && @exclude_end
55
+
56
+ true
27
57
  end
28
58
 
29
59
  # @return [Hash]
@@ -36,13 +66,9 @@ module Rucoa
36
66
 
37
67
  private
38
68
 
39
- # @param position [Rucoa::Position]
40
- # @return [Boolean]
41
- def exclude?(position)
42
- position.line > @ending.line ||
43
- position.line < @beginning.line ||
44
- (position.line == @beginning.line && position.column < @beginning.column) ||
45
- (position.line == @ending.line && position.column >= @ending.column)
69
+ # @return [Rucoa::Range]
70
+ def with_including_end
71
+ self.class.new(@beginning, @ending, exclude_end: false)
46
72
  end
47
73
  end
48
74
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ class RangeFormattingProvider
5
+ class << self
6
+ # @param range [Rucoa::Range]
7
+ # @param source [Rucoa::Source]
8
+ # @return [Array<Hash>]
9
+ def call(range:, source:)
10
+ new(range: range, source: source).call
11
+ end
12
+ end
13
+
14
+ # @param range [Rucoa::Range]
15
+ # @param source [Rucoa::Source]
16
+ def initialize(range:, source:)
17
+ @range = range
18
+ @source = source
19
+ end
20
+
21
+ # @return [Array<Hash>]
22
+ def call
23
+ return [] unless RubocopConfigurationChecker.call
24
+
25
+ edits
26
+ end
27
+
28
+ private
29
+
30
+ # @return [Array<RuboCop::Cop::Corrector>]
31
+ def correctable_offenses
32
+ offenses.select(&:corrector)
33
+ end
34
+
35
+ # @return [Array(Rucoa::Range, String)]
36
+ def correctable_replacements
37
+ replacements.select do |range, _|
38
+ @range.contains?(range)
39
+ end
40
+ end
41
+
42
+ # @return [Array<Hash>]
43
+ def edits
44
+ correctable_replacements.map do |range, replacement|
45
+ {
46
+ newText: replacement,
47
+ range: range.to_vscode_range
48
+ }
49
+ end
50
+ end
51
+
52
+ # @return [Array<RuboCop::Cop::Offense>]
53
+ def offenses
54
+ RubocopInvestigator.call(source: @source)
55
+ end
56
+
57
+ # @return [Array(Rucoa::Range, String)]
58
+ def replacements
59
+ correctable_offenses.map(&:corrector).flat_map(&:as_replacements).map do |range, replacement|
60
+ [
61
+ Range.from_parser_range(range),
62
+ replacement
63
+ ]
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module Rucoa
6
+ class RubocopConfigurationChecker
7
+ class << self
8
+ # @return [Boolean]
9
+ def call
10
+ new.call
11
+ end
12
+ end
13
+
14
+ # @return [Boolean]
15
+ def call
16
+ rubocop_configured_for_current_directory?
17
+ end
18
+
19
+ private
20
+
21
+ # @return [Boolean]
22
+ def rubocop_configured_for_current_directory?
23
+ each_current_and_ancestor_pathname.any? do |pathname|
24
+ pathname.join('.rubocop.yml').exist?
25
+ end
26
+ end
27
+
28
+ # @return [Enumerable<Pathname>]
29
+ def each_current_and_ancestor_pathname
30
+ return to_enum(__method__) unless block_given?
31
+
32
+ pathname = ::Pathname.pwd
33
+ loop do
34
+ yield pathname
35
+ break if pathname.root?
36
+
37
+ pathname = pathname.parent
38
+ end
39
+ self
40
+ end
41
+ end
42
+ end
@@ -74,7 +74,9 @@ module Rucoa
74
74
  expression_range
75
75
  ]
76
76
  else
77
- []
77
+ [
78
+ expression_range
79
+ ]
78
80
  end
79
81
  end
80
82
 
data/lib/rucoa/server.rb CHANGED
@@ -2,158 +2,113 @@
2
2
 
3
3
  module Rucoa
4
4
  class Server
5
- # @param reader [IO]
6
- # @param writer [IO]
7
- def initialize(reader:, writer:)
8
- @reader = MessageReader.new(reader)
9
- @writer = MessageWriter.new(writer)
5
+ # @return [Hash{String => Class}]
6
+ METHOD_TO_HANDLER_MAP = {
7
+ 'initialize' => Handlers::InitializeHandler,
8
+ 'initialized' => Handlers::InitializedHandler,
9
+ 'textDocument/codeAction' => Handlers::TextDocumentCodeActionHandler,
10
+ 'textDocument/didChange' => Handlers::TextDocumentDidChangeHandler,
11
+ 'textDocument/didOpen' => Handlers::TextDocumentDidOpenHandler,
12
+ 'textDocument/formatting' => Handlers::TextDocumentFormattingHandler,
13
+ 'textDocument/rangeFormatting' => Handlers::TextDocumentRangeFormattingHandler,
14
+ 'textDocument/selectionRange' => Handlers::TextDocumentSelectionRangeHandler,
15
+ 'workspace/didChangeConfiguration' => Handlers::WorkspaceDidChangeConfigurationHandler
16
+ }.freeze
17
+ private_constant :METHOD_TO_HANDLER_MAP
18
+
19
+ # @return [Rucoa::Configuration]
20
+ attr_reader :configuration
21
+
22
+ # @return [Rucoa::SourceStore]
23
+ attr_reader :source_store
24
+
25
+ # @param input [IO]
26
+ # @param output [IO]
27
+ def initialize(input:, output:)
28
+ @reader = MessageReader.new(input)
29
+ @writer = MessageWriter.new(output)
30
+
31
+ @configuration = Configuration.new
32
+ @server_request_id = 0
33
+ @client_response_handlers = {}
10
34
  @source_store = SourceStore.new
11
35
  end
12
36
 
13
37
  # @return [void]
14
38
  def start
15
- read do |request|
16
- result = handle(request)
17
- if result
18
- write(
19
- request: request,
20
- result: result
21
- )
22
- end
39
+ @reader.read do |request|
40
+ handle(request)
23
41
  end
24
42
  end
25
43
 
26
- private
27
-
28
- # @param request [Hash]
29
- # @return [Object]
30
- def handle(request)
31
- case request['method']
32
- when 'initialize'
33
- on_initialize(request)
34
- when 'textDocument/codeAction'
35
- on_text_document_code_action(request)
36
- when 'textDocument/didChange'
37
- on_text_document_did_change(request)
38
- when 'textDocument/didOpen'
39
- on_text_document_did_open(request)
40
- when 'textDocument/formatting'
41
- on_text_document_formatting(request)
42
- when 'textDocument/selectionRange'
43
- on_text_document_selection_range(request)
44
+ # @yieldparam response [Hash]
45
+ # @param message [Hash]
46
+ # @return [void]
47
+ def write(message, &block)
48
+ if block
49
+ write_server_request(message, &block)
50
+ else
51
+ write_server_response(message)
44
52
  end
45
53
  end
46
54
 
47
- # @yieldparam request [Hash]
48
- # @return [void]
49
- def read(&block)
50
- @reader.read(&block)
55
+ # @note This method is for testing.
56
+ # @return [Array<Hash>]
57
+ def responses
58
+ io = @writer.io
59
+ io.rewind
60
+ MessageReader.new(io).read.to_a
51
61
  end
52
62
 
53
- # @param request [Hash]
54
- # @param result [Object]
55
- # @return [void]
56
- def write(request:, result:)
57
- @writer.write(
58
- {
59
- id: request['id'],
60
- result: result
61
- }
62
- )
63
- end
63
+ private
64
64
 
65
- # @param uri [String]
65
+ # @param request [Hash]
66
66
  # @return [void]
67
- def investigate_diagnostics(uri:)
68
- @writer.write(
69
- method: 'textDocument/publishDiagnostics',
70
- params: {
71
- diagnostics: DiagnosticProvider.call(
72
- source: @source_store.get(uri),
73
- uri: uri
74
- ),
75
- uri: uri
76
- }
77
- )
78
- end
79
-
80
- # @param _request [Hash]
81
- # @return [Hash]
82
- def on_initialize(_request)
83
- {
84
- capabilities: {
85
- codeActionProvider: true,
86
- documentFormattingProvider: true,
87
- selectionRangeProvider: true,
88
- textDocumentSync: {
89
- change: 1, # Full
90
- openClose: true
91
- }
92
- }
93
- }
67
+ def handle(request)
68
+ if request['method']
69
+ handle_client_request(request)
70
+ elsif request['id']
71
+ handle_client_response(request)
72
+ end
94
73
  end
95
74
 
96
75
  # @param request [Hash]
97
- # @return [Array<Hash>, nil]
98
- def on_text_document_code_action(request)
99
- diagnostics = request.dig('params', 'context', 'diagnostics')
100
- return unless diagnostics
101
-
102
- CodeActionProvider.call(
103
- diagnostics: diagnostics
76
+ # @return [void]
77
+ def handle_client_request(request)
78
+ find_client_request_handler(request['method'])&.call(
79
+ request: request,
80
+ server: self
104
81
  )
105
82
  end
106
83
 
107
- # @param request [Hash]
108
- # @return [nil]
109
- def on_text_document_did_change(request)
110
- uri = request.dig('params', 'textDocument', 'uri')
111
- @source_store.set(
112
- uri,
113
- request.dig('params', 'contentChanges')[0]['text']
114
- )
115
- investigate_diagnostics(uri: uri)
116
- nil
84
+ # @param response [Hash]
85
+ # @return [void]
86
+ def handle_client_response(response)
87
+ @client_response_handlers.delete(response['id'])&.call(response)
117
88
  end
118
89
 
119
- # @param request [Hash]
120
- # @return [nil]
121
- def on_text_document_did_open(request)
122
- uri = request.dig('params', 'textDocument', 'uri')
123
- @source_store.set(
124
- uri,
125
- request.dig('params', 'textDocument', 'text')
126
- )
127
- investigate_diagnostics(uri: uri)
128
- nil
90
+ # @param request_method [String]
91
+ # @return [Class, nil]
92
+ def find_client_request_handler(request_method)
93
+ METHOD_TO_HANDLER_MAP[request_method]
129
94
  end
130
95
 
131
- # @param request [Hash]
132
- # @return [Array<Hash>, nil]
133
- def on_text_document_formatting(request)
134
- uri = request.dig('params', 'textDocument', 'uri')
135
- source = @source_store.get(uri)
136
- return unless source
137
-
138
- FormattingProvider.call(
139
- source: source
96
+ # @param message [Hash]
97
+ # @return [void]
98
+ def write_server_request(message, &block)
99
+ @writer.write(
100
+ message.merge(
101
+ id: @server_request_id
102
+ )
140
103
  )
104
+ @client_response_handlers[@server_request_id] = block
105
+ @server_request_id += 1
141
106
  end
142
107
 
143
- # @param request [Hash]
144
- # @return [Array<Hash>, nil]
145
- def on_text_document_selection_range(request)
146
- source = @source_store.get(
147
- request.dig('params', 'textDocument', 'uri')
148
- )
149
- return unless source
150
-
151
- request.dig('params', 'positions').filter_map do |position|
152
- SelectionRangeProvider.call(
153
- position: Position.from_vscode_position(position),
154
- source: source
155
- )
156
- end
108
+ # @param message [Hash]
109
+ # @return [void]
110
+ def write_server_response(message)
111
+ @writer.write(message)
157
112
  end
158
113
  end
159
114
  end
@@ -25,13 +25,19 @@ module Rucoa
25
25
  )
26
26
  end
27
27
 
28
+ # @yieldparam uri [String]
29
+ # @return [Enumerable<String>]
30
+ def each_uri(&block)
31
+ @data.each_key(&block)
32
+ end
33
+
28
34
  private
29
35
 
30
36
  # @param uri [String]
31
37
  # @return [String]
32
38
  def path_from_uri(uri)
33
39
  ::CGI.unescape(
34
- ::URI.parse(uri).path
40
+ ::URI.parse(uri).path || 'untitled'
35
41
  )
36
42
  end
37
43
  end
data/lib/rucoa/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rucoa
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/rucoa.rb CHANGED
@@ -5,9 +5,12 @@ require_relative 'rucoa/version'
5
5
  module Rucoa
6
6
  autoload :Cli, 'rucoa/cli'
7
7
  autoload :CodeActionProvider, 'rucoa/code_action_provider'
8
+ autoload :Configuration, 'rucoa/configuration'
8
9
  autoload :DiagnosticProvider, 'rucoa/diagnostic_provider'
9
10
  autoload :Errors, 'rucoa/errors'
10
11
  autoload :FormattingProvider, 'rucoa/formatting_provider'
12
+ autoload :HandlerConcerns, 'rucoa/handler_concerns'
13
+ autoload :Handlers, 'rucoa/handlers'
11
14
  autoload :MessageReader, 'rucoa/message_reader'
12
15
  autoload :MessageWriter, 'rucoa/message_writer'
13
16
  autoload :Nodes, 'rucoa/nodes'
@@ -15,7 +18,9 @@ module Rucoa
15
18
  autoload :ParserBuilder, 'rucoa/parser_builder'
16
19
  autoload :Position, 'rucoa/position'
17
20
  autoload :Range, 'rucoa/range'
21
+ autoload :RangeFormattingProvider, 'rucoa/range_formatting_provider'
18
22
  autoload :RubocopAutocorrector, 'rucoa/rubocop_autocorrector'
23
+ autoload :RubocopConfigurationChecker, 'rucoa/rubocop_configuration_checker'
19
24
  autoload :RubocopInvestigator, 'rucoa/rubocop_investigator'
20
25
  autoload :SelectionRangeProvider, 'rucoa/selection_range_provider'
21
26
  autoload :Server, 'rucoa/server'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rucoa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-04 00:00:00.000000000 Z
11
+ date: 2022-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -58,9 +58,24 @@ files:
58
58
  - lib/rucoa.rb
59
59
  - lib/rucoa/cli.rb
60
60
  - lib/rucoa/code_action_provider.rb
61
+ - lib/rucoa/configuration.rb
61
62
  - lib/rucoa/diagnostic_provider.rb
62
63
  - lib/rucoa/errors.rb
63
64
  - lib/rucoa/formatting_provider.rb
65
+ - lib/rucoa/handler_concerns.rb
66
+ - lib/rucoa/handler_concerns/configuration_requestable.rb
67
+ - lib/rucoa/handler_concerns/diagnostics_publishable.rb
68
+ - lib/rucoa/handlers.rb
69
+ - lib/rucoa/handlers/base.rb
70
+ - lib/rucoa/handlers/initialize_handler.rb
71
+ - lib/rucoa/handlers/initialized_handler.rb
72
+ - lib/rucoa/handlers/text_document_code_action_handler.rb
73
+ - lib/rucoa/handlers/text_document_did_change_handler.rb
74
+ - lib/rucoa/handlers/text_document_did_open_handler.rb
75
+ - lib/rucoa/handlers/text_document_formatting_handler.rb
76
+ - lib/rucoa/handlers/text_document_range_formatting_handler.rb
77
+ - lib/rucoa/handlers/text_document_selection_range_handler.rb
78
+ - lib/rucoa/handlers/workspace_did_change_configuration_handler.rb
64
79
  - lib/rucoa/message_reader.rb
65
80
  - lib/rucoa/message_writer.rb
66
81
  - lib/rucoa/nodes.rb
@@ -70,7 +85,9 @@ files:
70
85
  - lib/rucoa/parser_builder.rb
71
86
  - lib/rucoa/position.rb
72
87
  - lib/rucoa/range.rb
88
+ - lib/rucoa/range_formatting_provider.rb
73
89
  - lib/rucoa/rubocop_autocorrector.rb
90
+ - lib/rucoa/rubocop_configuration_checker.rb
74
91
  - lib/rucoa/rubocop_investigator.rb
75
92
  - lib/rucoa/selection_range_provider.rb
76
93
  - lib/rucoa/server.rb