rucoa 0.2.0 → 0.3.0

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