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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -2
- data/lib/rucoa/cli.rb +2 -2
- data/lib/rucoa/configuration.rb +63 -0
- data/lib/rucoa/diagnostic_provider.rb +12 -28
- data/lib/rucoa/formatting_provider.rb +2 -0
- data/lib/rucoa/handler_concerns/configuration_requestable.rb +35 -0
- data/lib/rucoa/handler_concerns/diagnostics_publishable.rb +32 -0
- data/lib/rucoa/handler_concerns.rb +8 -0
- data/lib/rucoa/handlers/base.rb +61 -0
- data/lib/rucoa/handlers/initialize_handler.rb +22 -0
- data/lib/rucoa/handlers/initialized_handler.rb +13 -0
- data/lib/rucoa/handlers/text_document_code_action_handler.rb +29 -0
- data/lib/rucoa/handlers/text_document_did_change_handler.rb +31 -0
- data/lib/rucoa/handlers/text_document_did_open_handler.rb +31 -0
- data/lib/rucoa/handlers/text_document_formatting_handler.rb +33 -0
- data/lib/rucoa/handlers/text_document_range_formatting_handler.rb +24 -0
- data/lib/rucoa/handlers/text_document_selection_range_handler.rb +25 -0
- data/lib/rucoa/handlers/workspace_did_change_configuration_handler.rb +13 -0
- data/lib/rucoa/handlers.rb +16 -0
- data/lib/rucoa/message_reader.rb +3 -1
- data/lib/rucoa/message_writer.rb +3 -0
- data/lib/rucoa/range.rb +35 -9
- data/lib/rucoa/range_formatting_provider.rb +67 -0
- data/lib/rucoa/rubocop_configuration_checker.rb +42 -0
- data/lib/rucoa/selection_range_provider.rb +3 -1
- data/lib/rucoa/server.rb +79 -124
- data/lib/rucoa/source_store.rb +7 -1
- data/lib/rucoa/version.rb +1 -1
- data/lib/rucoa.rb +5 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7206c3901e7ceea282b2de34f31ab90cc5ffa7fa48c06e58ae64bb718f48cd52
|
4
|
+
data.tar.gz: 6188a2047317b98160565599de3dc08663e732d7b54a546a8bc3e610379cb39a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afa9c37dae4a5c460eb1f5505ca511138c20f5b8c483a5ba68f798d1f9b18a5f5c4a481ee3ec743eb8585328538b5daec613b9f69a9599e5337106c57b1c0ea8
|
7
|
+
data.tar.gz: 539517254cff781a99b6ec463aed13bd04cbbcf3da3a9ff15cf72dac809b12c53ef9ff53c51828a163975bbdbe60a174767a472d2b4476f03f1813b5f513c62d
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -35,11 +35,13 @@ gem install rucoa
|
|
35
35
|
- Diagnostics
|
36
36
|
- Formatting
|
37
37
|
- Quick Fix
|
38
|
-
- Selection
|
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
@@ -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
|
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(
|
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
|
@@ -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,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,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,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
|
data/lib/rucoa/message_reader.rb
CHANGED
@@ -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
|
data/lib/rucoa/message_writer.rb
CHANGED
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
|
-
|
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
|
-
|
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
|
-
# @
|
40
|
-
|
41
|
-
|
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
|
data/lib/rucoa/server.rb
CHANGED
@@ -2,158 +2,113 @@
|
|
2
2
|
|
3
3
|
module Rucoa
|
4
4
|
class Server
|
5
|
-
# @
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
-
|
27
|
-
|
28
|
-
# @
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
# @
|
48
|
-
# @return [
|
49
|
-
def
|
50
|
-
@
|
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
|
-
|
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
|
65
|
+
# @param request [Hash]
|
66
66
|
# @return [void]
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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 [
|
98
|
-
def
|
99
|
-
|
100
|
-
|
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
|
108
|
-
# @return [
|
109
|
-
def
|
110
|
-
|
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
|
120
|
-
# @return [nil]
|
121
|
-
def
|
122
|
-
|
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
|
132
|
-
# @return [
|
133
|
-
def
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
144
|
-
# @return [
|
145
|
-
def
|
146
|
-
|
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
|
data/lib/rucoa/source_store.rb
CHANGED
@@ -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
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.
|
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-
|
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
|