holistic-ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +52 -0
- data/LICENSE.txt +21 -0
- data/README.md +35 -0
- data/Rakefile +8 -0
- data/config/logging.rb +6 -0
- data/exe/holistic-ruby +6 -0
- data/holistic-ruby.gemspec +34 -0
- data/lib/holistic/application.rb +29 -0
- data/lib/holistic/background_process.rb +11 -0
- data/lib/holistic/database/table.rb +78 -0
- data/lib/holistic/document/cursor.rb +9 -0
- data/lib/holistic/document/file.rb +36 -0
- data/lib/holistic/document/location.rb +35 -0
- data/lib/holistic/document/unsaved/change.rb +24 -0
- data/lib/holistic/document/unsaved/collection.rb +21 -0
- data/lib/holistic/document/unsaved/record.rb +83 -0
- data/lib/holistic/extensions/events.rb +37 -0
- data/lib/holistic/extensions/ruby/stdlib.rb +43 -0
- data/lib/holistic/language_server/current.rb +11 -0
- data/lib/holistic/language_server/format/file_uri.rb +19 -0
- data/lib/holistic/language_server/lifecycle.rb +59 -0
- data/lib/holistic/language_server/message.rb +21 -0
- data/lib/holistic/language_server/protocol.rb +45 -0
- data/lib/holistic/language_server/request.rb +21 -0
- data/lib/holistic/language_server/requests/lifecycle/exit.rb +10 -0
- data/lib/holistic/language_server/requests/lifecycle/initialize.rb +75 -0
- data/lib/holistic/language_server/requests/lifecycle/initialized.rb +13 -0
- data/lib/holistic/language_server/requests/lifecycle/shutdown.rb +14 -0
- data/lib/holistic/language_server/requests/text_document/completion.rb +68 -0
- data/lib/holistic/language_server/requests/text_document/did_change.rb +30 -0
- data/lib/holistic/language_server/requests/text_document/did_close.rb +33 -0
- data/lib/holistic/language_server/requests/text_document/did_open.rb +16 -0
- data/lib/holistic/language_server/requests/text_document/did_save.rb +33 -0
- data/lib/holistic/language_server/requests/text_document/find_references.rb +52 -0
- data/lib/holistic/language_server/requests/text_document/go_to_definition.rb +64 -0
- data/lib/holistic/language_server/response.rb +39 -0
- data/lib/holistic/language_server/router.rb +48 -0
- data/lib/holistic/language_server/stdio/parser.rb +65 -0
- data/lib/holistic/language_server/stdio/server.rb +46 -0
- data/lib/holistic/language_server/stdio/start.rb +48 -0
- data/lib/holistic/ruby/autocompletion/suggest.rb +75 -0
- data/lib/holistic/ruby/parser/constant_resolution.rb +61 -0
- data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +62 -0
- data/lib/holistic/ruby/parser/nesting_syntax.rb +76 -0
- data/lib/holistic/ruby/parser/program_visitor.rb +205 -0
- data/lib/holistic/ruby/parser/table_of_contents.rb +17 -0
- data/lib/holistic/ruby/parser.rb +26 -0
- data/lib/holistic/ruby/reference/find_referenced_scope.rb +18 -0
- data/lib/holistic/ruby/reference/record.rb +13 -0
- data/lib/holistic/ruby/reference/register.rb +15 -0
- data/lib/holistic/ruby/reference/repository.rb +71 -0
- data/lib/holistic/ruby/reference/unregister.rb +11 -0
- data/lib/holistic/ruby/scope/kind.rb +11 -0
- data/lib/holistic/ruby/scope/list_references.rb +32 -0
- data/lib/holistic/ruby/scope/location.rb +43 -0
- data/lib/holistic/ruby/scope/outline.rb +52 -0
- data/lib/holistic/ruby/scope/record.rb +52 -0
- data/lib/holistic/ruby/scope/register.rb +31 -0
- data/lib/holistic/ruby/scope/repository.rb +49 -0
- data/lib/holistic/ruby/scope/unregister.rb +27 -0
- data/lib/holistic/ruby/type_inference/clue/method_call.rb +15 -0
- data/lib/holistic/ruby/type_inference/clue/scope_reference.rb +13 -0
- data/lib/holistic/ruby/type_inference/conclusion.rb +20 -0
- data/lib/holistic/ruby/type_inference/solve.rb +110 -0
- data/lib/holistic/ruby/type_inference/solve_pending_references.rb +13 -0
- data/lib/holistic/version.rb +5 -0
- data/lib/holistic.rb +27 -0
- metadata +158 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
# Lifecycle constraints:
|
5
|
+
#
|
6
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
|
7
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#shutdown
|
8
|
+
class Lifecycle
|
9
|
+
UnexpectedStateError = ::Class.new(::StandardError)
|
10
|
+
|
11
|
+
attr_reader :state
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@state = :waiting_initialize_event
|
15
|
+
end
|
16
|
+
|
17
|
+
def waiting_initialized_event!
|
18
|
+
if @state != :waiting_initialize_event
|
19
|
+
raise UnexpectedStateError, "state must be :waiting_initialize_event, got: #{@state.inspect}"
|
20
|
+
end
|
21
|
+
|
22
|
+
@state = :waiting_initialized_event
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialized!
|
26
|
+
if @state != :waiting_initialized_event
|
27
|
+
raise UnexpectedStateError, "state must be :waiting_initialized_event, got: #{@state.inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
@state = :initialized
|
31
|
+
end
|
32
|
+
|
33
|
+
def shutdown!
|
34
|
+
if @state != :initialized
|
35
|
+
raise UnexpectedStateError, "state must be :initialized, got: #{@state.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
@state = :shutdown
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialized?
|
42
|
+
@state == :initialized || @state == :shutdown
|
43
|
+
end
|
44
|
+
|
45
|
+
def accept?(method)
|
46
|
+
return true if method == "exit"
|
47
|
+
return true if method == "initialize" && @state == :waiting_initialize_event
|
48
|
+
return true if method == "initialized" && @state == :waiting_initialized_event
|
49
|
+
|
50
|
+
return method != "initialize" && method != "initialized" if @state == :initialized
|
51
|
+
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def reject?(method)
|
56
|
+
!accept?(method)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
Message = ::Data.define(:data) do
|
5
|
+
def params
|
6
|
+
data["params"]
|
7
|
+
end
|
8
|
+
|
9
|
+
def param(*keys)
|
10
|
+
data["params"].dig(*keys)
|
11
|
+
end
|
12
|
+
|
13
|
+
def method
|
14
|
+
data["method"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def id
|
18
|
+
data["id"]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer::Protocol
|
4
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#abstractMessage
|
5
|
+
JSONRPC_VERSION = "2.0"
|
6
|
+
|
7
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#headerPart
|
8
|
+
END_OF_HEADER = "\r\n\r\n"
|
9
|
+
CONTENT_LENGTH_HEADER = "Content-Length"
|
10
|
+
|
11
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_synchronization
|
12
|
+
INCREMENTAL_TEXT_DOCUMENT_SYNCHRONIZATION = 2
|
13
|
+
|
14
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage
|
15
|
+
REQUEST_FAILED_ERROR_CODE = -32803
|
16
|
+
INVALID_REQUEST_ERROR_CODE = -32600
|
17
|
+
SERVER_NOT_INITIALIZED_ERROR_CODE = -32002
|
18
|
+
|
19
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind
|
20
|
+
COMPLETION_ITEM_KIND_TEXT = 1
|
21
|
+
COMPLETION_ITEM_KIND_METHOD = 2
|
22
|
+
COMPLETION_ITEM_KIND_FUNCTION = 3
|
23
|
+
COMPLETION_ITEM_KIND_CONSTRUCTOR = 4
|
24
|
+
COMPLETION_ITEM_KIND_FIELD = 5
|
25
|
+
COMPLETION_ITEM_KIND_VARIABLE = 6
|
26
|
+
COMPLETION_ITEM_KIND_CLASS = 7
|
27
|
+
COMPLETION_ITEM_KIND_INTERFACE = 8
|
28
|
+
COMPLETION_ITEM_KIND_MODULE = 9
|
29
|
+
COMPLETION_ITEM_KIND_PROPERTY = 10
|
30
|
+
COMPLETION_ITEM_KIND_UNIT = 11
|
31
|
+
COMPLETION_ITEM_KIND_VALUE = 12
|
32
|
+
COMPLETION_ITEM_KIND_ENUM = 13
|
33
|
+
COMPLETION_ITEM_KIND_KEYWORD = 14
|
34
|
+
COMPLETION_ITEM_KIND_SNIPPET = 15
|
35
|
+
COMPLETION_ITEM_KIND_COLOR = 16
|
36
|
+
COMPLETION_ITEM_KIND_FILE = 17
|
37
|
+
COMPLETION_ITEM_KIND_REFERENCE = 18
|
38
|
+
COMPLETION_ITEM_KIND_FOLDER = 19
|
39
|
+
COMPLETION_ITEM_KIND_ENUM_MEMBER = 20
|
40
|
+
COMPLETION_ITEM_KIND_CONSTANT = 21
|
41
|
+
COMPLETION_ITEM_KIND_STRUCT = 22
|
42
|
+
COMPLETION_ITEM_KIND_EVENT = 23
|
43
|
+
COMPLETION_ITEM_KIND_OPERATOR = 24
|
44
|
+
COMPLETION_ITEM_KIND_TYPE_PARAMTER = 25
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
Request = ::Struct.new(:message, :application, :response, keyword_init: true) do
|
5
|
+
def respond_with(result)
|
6
|
+
self.response = Response::Success.new(message_id: message.id, result:)
|
7
|
+
end
|
8
|
+
|
9
|
+
def respond_with_error(code:, description: nil, data: nil)
|
10
|
+
self.response = Response::Error.new(message_id: message.id, code:, message: description, data:)
|
11
|
+
end
|
12
|
+
|
13
|
+
def drop
|
14
|
+
self.response = Response::Drop.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def param(*keys)
|
18
|
+
message.param(*keys)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#exit
|
5
|
+
module Requests::Lifecycle::Exit
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def call(_request) = Response::Exit.new
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
|
5
|
+
module Requests::Lifecycle::Initialize
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# TODO: support multiple workspace directories.
|
9
|
+
|
10
|
+
def call(request)
|
11
|
+
application = create_application(request)
|
12
|
+
|
13
|
+
advance_lifecycle_state
|
14
|
+
|
15
|
+
parse_application_in_background(application)
|
16
|
+
|
17
|
+
respond_with_holistic_capabilities(request)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_application(request)
|
23
|
+
::Holistic.logger.info("===========")
|
24
|
+
::Holistic.logger.info(request.message.inspect)
|
25
|
+
|
26
|
+
root_directory = request.param("rootPath")
|
27
|
+
name = ::File.basename(root_directory)
|
28
|
+
|
29
|
+
Current.application = ::Holistic::Application.new(name:, root_directory:)
|
30
|
+
|
31
|
+
::Holistic::Extensions::Ruby::Stdlib.register(Current.application)
|
32
|
+
|
33
|
+
Current.application
|
34
|
+
end
|
35
|
+
|
36
|
+
def advance_lifecycle_state
|
37
|
+
Current.lifecycle.waiting_initialized_event!
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_application_in_background(application)
|
41
|
+
::Holistic::BackgroundProcess.run do
|
42
|
+
::Holistic::Ruby::Parser::ParseDirectory.call(application:, directory_path: application.root_directory)
|
43
|
+
|
44
|
+
::Holistic::Ruby::TypeInference::SolvePendingReferences.call(application:)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def respond_with_holistic_capabilities(request)
|
49
|
+
request.respond_with({
|
50
|
+
capabilities: {
|
51
|
+
# Defines how the host (editor) should sync document changes to the language server.
|
52
|
+
# Incremental means documents are synced by sending the full content on open.
|
53
|
+
# After that only incremental updates to the document are sent.
|
54
|
+
textDocumentSync: Protocol::INCREMENTAL_TEXT_DOCUMENT_SYNCHRONIZATION,
|
55
|
+
|
56
|
+
# The server provides goto definition support.
|
57
|
+
definitionProvider: true,
|
58
|
+
|
59
|
+
# The server provides find references support.
|
60
|
+
referencesProvider: true,
|
61
|
+
|
62
|
+
# The server provides completion support
|
63
|
+
completionProvider: {
|
64
|
+
triggerCharacters: [".", "@"],
|
65
|
+
resolveProvider: true
|
66
|
+
}
|
67
|
+
},
|
68
|
+
serverInfo: {
|
69
|
+
name: "Holistic Ruby",
|
70
|
+
version: ::Holistic::VERSION
|
71
|
+
}
|
72
|
+
})
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#shutdown
|
5
|
+
module Requests::Lifecycle::Shutdown
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def call(request)
|
9
|
+
Current.lifecycle.shutdown!
|
10
|
+
|
11
|
+
request.respond_with(nil)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
module Requests::TextDocument::Completion
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(request)
|
8
|
+
cursor = build_cursor_from_request_params(request)
|
9
|
+
|
10
|
+
document = request.application.unsaved_documents.find(cursor.file_path)
|
11
|
+
|
12
|
+
return request.respond_with(nil) if document.nil?
|
13
|
+
|
14
|
+
if document.has_unsaved_changes?
|
15
|
+
::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
|
16
|
+
application: request.application,
|
17
|
+
file: document.to_file
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
code = document.expand_code(cursor)
|
22
|
+
scope = request.application.scopes.find_inner_most_scope_by_cursor(cursor) || request.application.root_scope
|
23
|
+
|
24
|
+
return request.respond_with(nil) if code.blank?
|
25
|
+
|
26
|
+
suggestions = ::Holistic::Ruby::Autocompletion::Suggest.call(code:, scope:)
|
27
|
+
|
28
|
+
respond_with_suggestions(request, suggestions)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_cursor_from_request_params(request)
|
34
|
+
file_path = Format::FileUri.extract_path(request.param("textDocument", "uri"))
|
35
|
+
line = request.param("position", "line")
|
36
|
+
column = request.param("position", "character")
|
37
|
+
|
38
|
+
::Holistic::Document::Cursor.new(file_path:, line:, column:)
|
39
|
+
end
|
40
|
+
|
41
|
+
module CompletionKind
|
42
|
+
FROM_SCOPE_TO_COMPLETION = {
|
43
|
+
::Holistic::Ruby::Scope::Kind::CLASS => Protocol::COMPLETION_ITEM_KIND_CLASS,
|
44
|
+
::Holistic::Ruby::Scope::Kind::LAMBDA => Protocol::COMPLETION_ITEM_KIND_FUNCTION,
|
45
|
+
::Holistic::Ruby::Scope::Kind::METHOD => Protocol::COMPLETION_ITEM_KIND_METHOD,
|
46
|
+
::Holistic::Ruby::Scope::Kind::MODULE => Protocol::COMPLETION_ITEM_KIND_MODULE,
|
47
|
+
::Holistic::Ruby::Scope::Kind::ROOT => Protocol::COMPLETION_ITEM_KIND_MODULE
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
DEFAULT = Protocol::COMPLETION_ITEM_KIND_MODULE
|
51
|
+
|
52
|
+
def self.fetch(scope_kind)
|
53
|
+
FROM_SCOPE_TO_COMPLETION.fetch(scope_kind, DEFAULT)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def respond_with_suggestions(request, suggestions)
|
58
|
+
formatted_suggestions = suggestions.map do |suggestion|
|
59
|
+
{
|
60
|
+
label: suggestion.code,
|
61
|
+
kind: CompletionKind.fetch(suggestion.kind)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
request.respond_with(formatted_suggestions)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
module Requests::TextDocument::DidChange
|
5
|
+
extend self
|
6
|
+
|
7
|
+
BuildDocumentChange = ->(params) do
|
8
|
+
::Holistic::Document::Unsaved::Change.new(
|
9
|
+
range_length: params.dig("rangeLength"),
|
10
|
+
text: params.dig("text"),
|
11
|
+
start_line: params.dig("range", "start", "line"),
|
12
|
+
start_column: params.dig("range", "start", "character"),
|
13
|
+
end_line: params.dig("range", "end", "line"),
|
14
|
+
end_column: params.dig("range", "end", "character")
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(request)
|
19
|
+
file_path = Format::FileUri.extract_path(request.param("textDocument", "uri"))
|
20
|
+
|
21
|
+
unsaved_document = request.application.unsaved_documents.find(file_path)
|
22
|
+
|
23
|
+
request.param("contentChanges").map(&BuildDocumentChange).each do |change|
|
24
|
+
unsaved_document.apply_change(change)
|
25
|
+
end
|
26
|
+
|
27
|
+
request.respond_with(nil)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
module Requests::TextDocument::DidClose
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(request)
|
8
|
+
path = Format::FileUri.extract_path(request.message.param("textDocument", "uri"))
|
9
|
+
|
10
|
+
unsaved_document = request.application.unsaved_documents.find(path)
|
11
|
+
|
12
|
+
if unsaved_document.present?
|
13
|
+
request.application.unsaved_documents.delete(path)
|
14
|
+
|
15
|
+
if unsaved_document.has_unsaved_changes?
|
16
|
+
unsaved_document.restore_original_content!
|
17
|
+
|
18
|
+
process_in_background(application: request.application, file: unsaved_document.to_file)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
request.respond_with(nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def process_in_background(application:, file:)
|
28
|
+
::Holistic::BackgroundProcess.run do
|
29
|
+
::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(application:, file:)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
module Requests::TextDocument::DidOpen
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(request)
|
8
|
+
path = Format::FileUri.extract_path(request.message.param("textDocument", "uri"))
|
9
|
+
content = request.message.param("textDocument", "text")
|
10
|
+
|
11
|
+
request.application.unsaved_documents.add(path:, content:)
|
12
|
+
|
13
|
+
request.respond_with(nil)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
module Requests::TextDocument::DidSave
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(request)
|
8
|
+
file_path = Format::FileUri.extract_path(request.param("textDocument", "uri"))
|
9
|
+
unsaved_document = request.application.unsaved_documents.find(file_path)
|
10
|
+
|
11
|
+
if unsaved_document.nil?
|
12
|
+
return request.respond_with_error(
|
13
|
+
code: Protocol::REQUEST_FAILED_ERROR_CODE,
|
14
|
+
description: "could not find document #{file_path} in the unsaved documents list"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
unsaved_document.mark_as_saved!
|
19
|
+
|
20
|
+
process_in_background(application: request.application, file: unsaved_document.to_file)
|
21
|
+
|
22
|
+
request.respond_with(nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def process_in_background(application:, file:)
|
28
|
+
::Holistic::BackgroundProcess.run do
|
29
|
+
::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(application:, file:)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references
|
5
|
+
module Requests::TextDocument::FindReferences
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def call(request)
|
9
|
+
cursor = build_cursor_from_params(request)
|
10
|
+
|
11
|
+
request.application.unsaved_documents.find(cursor.file_path)&.then do |unsaved_document|
|
12
|
+
if unsaved_document.has_unsaved_changes?
|
13
|
+
::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
|
14
|
+
application: request.application,
|
15
|
+
file: unsaved_document.to_file
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
case ::Holistic::Ruby::Scope::ListReferences.call(application: request.application, cursor:)
|
21
|
+
in :not_found
|
22
|
+
request.respond_with(nil)
|
23
|
+
in [:references_listed, {references:}]
|
24
|
+
respond_with_locations(request, references)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def build_cursor_from_params(request)
|
31
|
+
file_path = Format::FileUri.extract_path(request.param("textDocument", "uri"))
|
32
|
+
line = request.param("position", "line")
|
33
|
+
column = request.param("position", "character")
|
34
|
+
|
35
|
+
::Holistic::Document::Cursor.new(file_path:, line:, column:)
|
36
|
+
end
|
37
|
+
|
38
|
+
def respond_with_locations(request, references)
|
39
|
+
locations = references.map do |reference|
|
40
|
+
{
|
41
|
+
"uri" => Format::FileUri.from_path(reference.location.file_path),
|
42
|
+
"range" => {
|
43
|
+
"start" => { "line" => reference.location.start_line, "character" => reference.location.start_column },
|
44
|
+
"end" => { "line" => reference.location.end_line, "character" => reference.location.end_column }
|
45
|
+
}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
request.respond_with(locations)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition
|
5
|
+
module Requests::TextDocument::GoToDefinition
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def call(request)
|
9
|
+
cursor = build_cursor_from_params(request)
|
10
|
+
|
11
|
+
request.application.unsaved_documents.find(cursor.file_path)&.then do |unsaved_document|
|
12
|
+
if unsaved_document.has_unsaved_changes?
|
13
|
+
::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
|
14
|
+
application: request.application,
|
15
|
+
file: unsaved_document.to_file
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
case ::Holistic::Ruby::Reference::FindReferencedScope.call(application: request.application, cursor:)
|
21
|
+
in :not_found
|
22
|
+
request.respond_with(nil)
|
23
|
+
in :could_not_find_referenced_scope
|
24
|
+
request.respond_with(nil)
|
25
|
+
in [:referenced_scope_found, {reference:, referenced_scope:}]
|
26
|
+
respond_with_location_link(request, reference, referenced_scope)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_cursor_from_params(request)
|
33
|
+
file_path = Format::FileUri.extract_path(request.param("textDocument", "uri"))
|
34
|
+
line = request.param("position", "line")
|
35
|
+
column = request.param("position", "character")
|
36
|
+
|
37
|
+
::Holistic::Document::Cursor.new(file_path:, line:, column:)
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_with_location_link(request, reference, referenced_scope)
|
41
|
+
origin_location = reference.location
|
42
|
+
target_declaration_location = referenced_scope.locations.main.declaration
|
43
|
+
|
44
|
+
location_link = {
|
45
|
+
"originSelectionRange" => {
|
46
|
+
"start" => { "line" => origin_location.start_line, "character" => origin_location.start_column },
|
47
|
+
"end" => { "line" => origin_location.end_line, "character" => origin_location.end_column }
|
48
|
+
},
|
49
|
+
"targetUri" => Format::FileUri.from_path(target_declaration_location.file_path),
|
50
|
+
"targetRange" => {
|
51
|
+
"start" => { "line" => target_declaration_location.start_line, "character" => target_declaration_location.start_column },
|
52
|
+
"end" => { "line" => target_declaration_location.end_line, "character" => target_declaration_location.end_column }
|
53
|
+
},
|
54
|
+
# TODO: store the location of the declaration name
|
55
|
+
"targetSelectionRange" => {
|
56
|
+
"start" => { "line" => target_declaration_location.start_line, "character" => target_declaration_location.start_column },
|
57
|
+
"end" => { "line" => target_declaration_location.end_line, "character" => target_declaration_location.end_column }
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
request.respond_with([location_link])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
module Response
|
5
|
+
extend self
|
6
|
+
|
7
|
+
Success = ::Data.define(:message_id, :result) do
|
8
|
+
def encode
|
9
|
+
encoded_payload = {
|
10
|
+
"jsonrpc" => Protocol::JSONRPC_VERSION,
|
11
|
+
"id" => message_id,
|
12
|
+
"result" => result
|
13
|
+
}.to_json
|
14
|
+
|
15
|
+
"#{Protocol::CONTENT_LENGTH_HEADER}:#{encoded_payload.bytesize}#{Protocol::END_OF_HEADER}#{encoded_payload}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Error = ::Data.define(:message_id, :code, :message, :data) do
|
20
|
+
def encode
|
21
|
+
encoded_payload = {
|
22
|
+
"jsonrpc" => Protocol::JSONRPC_VERSION,
|
23
|
+
"id" => message_id,
|
24
|
+
"error" => {
|
25
|
+
"code" => code,
|
26
|
+
"message" => message,
|
27
|
+
"data" => data
|
28
|
+
}
|
29
|
+
}.to_json
|
30
|
+
|
31
|
+
"#{Protocol::CONTENT_LENGTH_HEADER}:#{encoded_payload.bytesize}#{Protocol::END_OF_HEADER}#{encoded_payload}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Exit = ::Data.define
|
36
|
+
NotFound = ::Data.define
|
37
|
+
Drop = ::Data.define
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer
|
4
|
+
module Router
|
5
|
+
extend self
|
6
|
+
|
7
|
+
ROUTES = {
|
8
|
+
"initialize" => Requests::Lifecycle::Initialize,
|
9
|
+
"initialized" => Requests::Lifecycle::Initialized,
|
10
|
+
"shutdown" => Requests::Lifecycle::Shutdown,
|
11
|
+
"exit" => Requests::Lifecycle::Exit,
|
12
|
+
"textDocument/didOpen" => Requests::TextDocument::DidOpen,
|
13
|
+
"textDocument/didChange" => Requests::TextDocument::DidChange,
|
14
|
+
"textDocument/didSave" => Requests::TextDocument::DidSave,
|
15
|
+
"textDocument/didClose" => Requests::TextDocument::DidClose,
|
16
|
+
"textDocument/definition" => Requests::TextDocument::GoToDefinition,
|
17
|
+
"textDocument/references" => Requests::TextDocument::FindReferences,
|
18
|
+
"textDocument/completion" => Requests::TextDocument::Completion
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def dispatch(message)
|
22
|
+
request = Request.new(message:, application: Current.application)
|
23
|
+
|
24
|
+
::ActiveSupport::Notifications.instrument("holistic.language_server.request", request:) do
|
25
|
+
return respond_with_rejection(request) if Current.lifecycle.reject?(message.method)
|
26
|
+
|
27
|
+
handler = ROUTES[message.method]
|
28
|
+
|
29
|
+
return Response::NotFound.new if handler.nil?
|
30
|
+
|
31
|
+
handler.call(request)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def respond_with_rejection(request)
|
38
|
+
error_code =
|
39
|
+
if Current.lifecycle.initialized?
|
40
|
+
Protocol::INVALID_REQUEST_ERROR_CODE
|
41
|
+
else
|
42
|
+
Protocol::SERVER_NOT_INITIALIZED_ERROR_CODE
|
43
|
+
end
|
44
|
+
|
45
|
+
request.respond_with_error(code: error_code)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|