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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: aca9fe7ec2638ae0f1b641f4a0ea62befe91fb6c77820112cf4d8def6287c9d1
|
4
|
+
data.tar.gz: f2a89f2821815944c584307994e671d0203dd097f2301bccc652385f68100eb7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5a8d65f2ed3816391a706015588856ca5c4a92c3bf6490b505328135765530b68243fec0231c5fea38fbfc97c494255fd23865bddde59db52d7d47a910d4d887
|
7
|
+
data.tar.gz: a1bc0c055c364692c1ae6de33bb1a270234af95c99e6be708dac6ac069b58b29ca24802f419de1328a9aabb133417c750592a7375b144d349bbfe86fe4dcbf41
|
data/.rspec
ADDED
data/.standard.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
holistic-ruby (0.1.0)
|
5
|
+
activesupport (~> 7.0)
|
6
|
+
syntax_tree (~> 6.0)
|
7
|
+
zeitwerk (~> 2.6)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (7.0.7)
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
+
i18n (>= 1.6, < 2)
|
15
|
+
minitest (>= 5.1)
|
16
|
+
tzinfo (~> 2.0)
|
17
|
+
concurrent-ruby (1.2.2)
|
18
|
+
diff-lcs (1.5.0)
|
19
|
+
i18n (1.14.1)
|
20
|
+
concurrent-ruby (~> 1.0)
|
21
|
+
minitest (5.19.0)
|
22
|
+
prettier_print (1.2.1)
|
23
|
+
rake (13.0.6)
|
24
|
+
rspec (3.12.0)
|
25
|
+
rspec-core (~> 3.12.0)
|
26
|
+
rspec-expectations (~> 3.12.0)
|
27
|
+
rspec-mocks (~> 3.12.0)
|
28
|
+
rspec-core (3.12.1)
|
29
|
+
rspec-support (~> 3.12.0)
|
30
|
+
rspec-expectations (3.12.2)
|
31
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
+
rspec-support (~> 3.12.0)
|
33
|
+
rspec-mocks (3.12.3)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.12.0)
|
36
|
+
rspec-support (3.12.0)
|
37
|
+
syntax_tree (6.1.1)
|
38
|
+
prettier_print (>= 1.2.0)
|
39
|
+
tzinfo (2.0.6)
|
40
|
+
concurrent-ruby (~> 1.0)
|
41
|
+
zeitwerk (2.6.11)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
x86_64-linux
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
holistic-ruby!
|
48
|
+
rake (~> 13.0)
|
49
|
+
rspec (~> 3.0)
|
50
|
+
|
51
|
+
BUNDLED WITH
|
52
|
+
2.4.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Luiz Vasconcellos
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# holistic-ruby
|
2
|
+
|
3
|
+
`holistic-ruby` is a toy language server for the Ruby programming language.
|
4
|
+
|
5
|
+
## Installation for Sublime Text
|
6
|
+
|
7
|
+
1. Make sure you have the [LSP package installed](https://github.com/sublimelsp/LSP).
|
8
|
+
2. Install the gem `$ gem install holistic-ruby`
|
9
|
+
3. Go to `Preferences > Package Settings > LSP > Settings` and add:
|
10
|
+
|
11
|
+
```json
|
12
|
+
{
|
13
|
+
"clients": {
|
14
|
+
"holistic-ruby": {
|
15
|
+
"enabled": true,
|
16
|
+
"command": ["holistic-ruby"],
|
17
|
+
"selector": "source.ruby | text.html.ruby",
|
18
|
+
"initializationOptions": {}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
```
|
23
|
+
|
24
|
+
## Features
|
25
|
+
|
26
|
+
* Go to definition.
|
27
|
+
* Find references.
|
28
|
+
* Autocompletion for namespaces, methods and local variables.
|
29
|
+
* Syntax highlighting boundaries based on packwerk.
|
30
|
+
* Outline dependencies.
|
31
|
+
* Glossary.
|
32
|
+
|
33
|
+
## Why is it a toy language server?
|
34
|
+
|
35
|
+
I use `holistic-ruby` on a daily basis in a faily large Ruby codebase. It seems stable and speedy. But... I built it for myself and I'm the only one using it :smile:
|
data/Rakefile
ADDED
data/config/logging.rb
ADDED
data/exe/holistic-ruby
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/holistic/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "holistic-ruby"
|
7
|
+
spec.version = Holistic::VERSION
|
8
|
+
spec.authors = ["Luiz Vasconcellos"]
|
9
|
+
spec.email = ["luizpvasc@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Static analysis for Ruby"
|
12
|
+
spec.description = "Static analysis for Ruby"
|
13
|
+
spec.homepage = "https://github.com/luizpvas/holistic-ruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency "syntax_tree", "~> 6.0"
|
32
|
+
spec.add_dependency "zeitwerk", "~> 2.6"
|
33
|
+
spec.add_dependency "activesupport", "~> 7.0"
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic
|
4
|
+
class Application
|
5
|
+
attr_reader :name, :root_directory, :root_scope
|
6
|
+
|
7
|
+
def initialize(name:, root_directory:)
|
8
|
+
@name = name
|
9
|
+
@root_directory = root_directory
|
10
|
+
@root_scope = Ruby::Scope::Record.new(kind: Ruby::Scope::Kind::ROOT, name: "::", parent: nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
def extensions
|
14
|
+
@extensions ||= Extensions::Events.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def scopes
|
18
|
+
@scopes ||= Ruby::Scope::Repository.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def references
|
22
|
+
@references ||= Ruby::Reference::Repository.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def unsaved_documents
|
26
|
+
@unsaved_documents ||= Document::Unsaved::Collection.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Holistic::Database::Table
|
4
|
+
attr_reader :primary_attribute, :primary_index, :secondary_indices
|
5
|
+
|
6
|
+
def initialize(primary_attribute:, indices: [])
|
7
|
+
@primary_attribute = primary_attribute
|
8
|
+
|
9
|
+
@primary_index = ::Hash.new
|
10
|
+
|
11
|
+
@secondary_indices = indices.map do |attribute_name|
|
12
|
+
[attribute_name, ::Hash.new { |hash, key| hash[key] = ::Set.new }]
|
13
|
+
end.to_h
|
14
|
+
end
|
15
|
+
|
16
|
+
RecordNotUniqueError = ::Class.new(::StandardError)
|
17
|
+
|
18
|
+
def insert(record)
|
19
|
+
primary_key = record.fetch(primary_attribute)
|
20
|
+
|
21
|
+
if primary_index.key?(primary_key)
|
22
|
+
raise RecordNotUniqueError, "record already inserted: #{record.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
primary_index[primary_key] = record
|
26
|
+
|
27
|
+
secondary_indices.each do |attribute_name, secondary_index|
|
28
|
+
Array(record[attribute_name]).each do |value|
|
29
|
+
secondary_index[value].add(primary_key)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def find(identifier)
|
35
|
+
primary_index[identifier]
|
36
|
+
end
|
37
|
+
|
38
|
+
def filter(name, value)
|
39
|
+
return [] unless secondary_indices[name].key?(value)
|
40
|
+
|
41
|
+
secondary_indices.dig(name, value).to_a.map { find(_1) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def update(record)
|
45
|
+
primary_key = record.fetch(primary_attribute)
|
46
|
+
|
47
|
+
delete(primary_key)
|
48
|
+
|
49
|
+
insert(record)
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete(primary_key)
|
53
|
+
record = find(primary_key)
|
54
|
+
|
55
|
+
return if record.nil?
|
56
|
+
|
57
|
+
primary_index.delete(primary_key)
|
58
|
+
|
59
|
+
secondary_indices.each do |attribute_name, index_data|
|
60
|
+
Array(record[attribute_name]).each do |value|
|
61
|
+
index_data[value].delete(primary_key)
|
62
|
+
index_data.delete(value) if index_data[value].empty?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
record
|
67
|
+
end
|
68
|
+
|
69
|
+
concerning :TestHelpers do
|
70
|
+
def all
|
71
|
+
primary_index.values
|
72
|
+
end
|
73
|
+
|
74
|
+
def size
|
75
|
+
primary_index.size
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Document
|
4
|
+
class File
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
def initialize(path:)
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def read
|
12
|
+
::File.read(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def write(content)
|
16
|
+
::File.write(path, content)
|
17
|
+
end
|
18
|
+
|
19
|
+
class Fake
|
20
|
+
attr_reader :path
|
21
|
+
|
22
|
+
def initialize(path:, content:)
|
23
|
+
@path = path
|
24
|
+
@content = content
|
25
|
+
end
|
26
|
+
|
27
|
+
def read
|
28
|
+
@content
|
29
|
+
end
|
30
|
+
|
31
|
+
def write(content)
|
32
|
+
@content = content
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Document
|
4
|
+
Location = ::Data.define(
|
5
|
+
:file_path,
|
6
|
+
:start_line,
|
7
|
+
:start_column,
|
8
|
+
:end_line,
|
9
|
+
:end_column
|
10
|
+
) do
|
11
|
+
def self.beginning_of_file(file_path)
|
12
|
+
new(file_path, 0, 0, 0, 0)
|
13
|
+
end
|
14
|
+
|
15
|
+
def identifier = "#{file_path}[#{start_line},#{start_column},#{end_line},#{end_column}]"
|
16
|
+
|
17
|
+
def contains?(cursor)
|
18
|
+
same_file = cursor.file_path == file_path
|
19
|
+
contains_line = cursor.line >= start_line && cursor.line <= end_line
|
20
|
+
|
21
|
+
contains_column =
|
22
|
+
if start_line == end_line
|
23
|
+
cursor.column > start_column && cursor.column <= end_column
|
24
|
+
elsif start_line == cursor.line
|
25
|
+
cursor.column > start_column
|
26
|
+
elsif end_line == cursor.line
|
27
|
+
cursor.column <= end_column
|
28
|
+
else
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
same_file && contains_line && contains_column
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Document::Unsaved
|
4
|
+
Change = ::Data.define(
|
5
|
+
:range_length,
|
6
|
+
:text,
|
7
|
+
:start_line,
|
8
|
+
:start_column,
|
9
|
+
:end_line,
|
10
|
+
:end_column
|
11
|
+
) do
|
12
|
+
def insertion?
|
13
|
+
text.size.positive? && start_line == end_line && start_column == end_column
|
14
|
+
end
|
15
|
+
|
16
|
+
def deletion?
|
17
|
+
text.empty? && (start_line != end_line || start_column != end_column)
|
18
|
+
end
|
19
|
+
|
20
|
+
def starts_on?(line, column)
|
21
|
+
start_line == line && start_column == column
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Document
|
4
|
+
class Unsaved::Collection
|
5
|
+
def initialize
|
6
|
+
@items = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(path:, content:)
|
10
|
+
@items[path] = Unsaved::Record.new(path:, content:)
|
11
|
+
end
|
12
|
+
|
13
|
+
def delete(path)
|
14
|
+
@items.delete(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(path)
|
18
|
+
@items[path]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Document
|
4
|
+
class Unsaved::Record
|
5
|
+
attr_reader :path, :content
|
6
|
+
|
7
|
+
def initialize(path:, content:)
|
8
|
+
@path = path
|
9
|
+
@content = content
|
10
|
+
@original_content = content.dup
|
11
|
+
end
|
12
|
+
|
13
|
+
def expand_code(cursor)
|
14
|
+
line = 0
|
15
|
+
column = 0
|
16
|
+
|
17
|
+
@content.each_char.with_index do |char, index|
|
18
|
+
if cursor.line == line && cursor.column == column + 1
|
19
|
+
token_index = index
|
20
|
+
|
21
|
+
while @content[token_index].match?(/[a-zA-Z0-9_\.:]/)
|
22
|
+
token_index -= 1
|
23
|
+
end
|
24
|
+
|
25
|
+
return @content[token_index+1..index]
|
26
|
+
end
|
27
|
+
|
28
|
+
if char == LINE_BREAK
|
29
|
+
line += 1
|
30
|
+
column = 0
|
31
|
+
else
|
32
|
+
column += 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def mark_as_saved!
|
40
|
+
@original_content = @content.dup
|
41
|
+
end
|
42
|
+
|
43
|
+
def restore_original_content!
|
44
|
+
@content = @original_content.dup
|
45
|
+
end
|
46
|
+
|
47
|
+
def has_unsaved_changes?
|
48
|
+
@original_content != @content
|
49
|
+
end
|
50
|
+
|
51
|
+
LINE_BREAK = "\n"
|
52
|
+
|
53
|
+
def apply_change(change)
|
54
|
+
line = 0
|
55
|
+
column = 0
|
56
|
+
|
57
|
+
@content.each_char.with_index do |char, index|
|
58
|
+
if change.insertion? && change.starts_on?(line, column)
|
59
|
+
content.insert(index, change.text)
|
60
|
+
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
if change.deletion? && change.starts_on?(line, column)
|
65
|
+
content[index..index + change.range_length - 1] = ""
|
66
|
+
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
if char == LINE_BREAK
|
71
|
+
line += 1
|
72
|
+
column = 0
|
73
|
+
else
|
74
|
+
column += 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_file
|
80
|
+
File::Fake.new(path:, content:)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Holistic::Extensions::Events
|
4
|
+
TOPICS = {
|
5
|
+
resolve_method_call_known_scope: {
|
6
|
+
params: [:reference, :referenced_scope, :method_call_clue],
|
7
|
+
output: ::Holistic::Ruby::Scope::Record
|
8
|
+
}
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
UnknownEvent = ::Class.new(::StandardError)
|
12
|
+
MissingRequiredParam = ::Class.new(::StandardError)
|
13
|
+
UnexpectedOutput = ::Class.new(::StandardError)
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@listeners = Hash.new { |hash, key| hash[key] = [] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def bind(event, &callback)
|
20
|
+
raise UnknownEvent, event unless TOPICS.key?(event)
|
21
|
+
|
22
|
+
@listeners[event] << callback
|
23
|
+
end
|
24
|
+
|
25
|
+
def dispatch(event, params)
|
26
|
+
required_params = TOPICS.dig(event, :params)
|
27
|
+
expected_output = TOPICS.dig(event, :output)
|
28
|
+
|
29
|
+
raise MissingRequiredParam, required_params if (required_params - params.keys).any?
|
30
|
+
|
31
|
+
result = @listeners[event].lazy.filter_map { |callback| callback.call(params) }.first
|
32
|
+
|
33
|
+
raise UnexpectedOutput, result if result.present? && !result.is_a?(expected_output)
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Extensions::Ruby
|
4
|
+
module Stdlib
|
5
|
+
extend self
|
6
|
+
|
7
|
+
ResolveClassConstructor = ->(application, params) do
|
8
|
+
method_call_clue, referenced_scope = params[:method_call_clue], params[:referenced_scope]
|
9
|
+
|
10
|
+
if method_call_clue.method_name == "new" && referenced_scope.class?
|
11
|
+
initialize_method = "#{referenced_scope.fully_qualified_name}#initialize"
|
12
|
+
|
13
|
+
return application.scopes.find_by_fully_qualified_name(initialize_method)
|
14
|
+
end
|
15
|
+
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
ResolveStaticMethods = ->(application, params) do
|
20
|
+
method_call_clue, referenced_scope = params[:method_call_clue], params[:referenced_scope]
|
21
|
+
|
22
|
+
self_method_name = "#{referenced_scope.fully_qualified_name}#self.#{method_call_clue.method_name}"
|
23
|
+
|
24
|
+
application.scopes.find_by_fully_qualified_name(self_method_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
LAMBDA_METHODS = ["call", "curry"]
|
28
|
+
|
29
|
+
ResolveCallToLambda = ->(application, params) do
|
30
|
+
method_call_clue, referenced_scope = params[:method_call_clue], params[:referenced_scope]
|
31
|
+
|
32
|
+
if LAMBDA_METHODS.include?(method_call_clue.method_name) && referenced_scope.lambda?
|
33
|
+
return referenced_scope
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def register(application)
|
38
|
+
application.extensions.bind(:resolve_method_call_known_scope, &ResolveClassConstructor.curry[application])
|
39
|
+
application.extensions.bind(:resolve_method_call_known_scope, &ResolveStaticMethods.curry[application])
|
40
|
+
application.extensions.bind(:resolve_method_call_known_scope, &ResolveCallToLambda.curry[application])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/isolated_execution_state"
|
4
|
+
require "active_support/code_generator"
|
5
|
+
require "active_support/current_attributes"
|
6
|
+
|
7
|
+
module Holistic::LanguageServer
|
8
|
+
class Current < ::ActiveSupport::CurrentAttributes
|
9
|
+
attribute :application, :lifecycle
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::LanguageServer::Format
|
4
|
+
module FileUri
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def extract_path(file_uri)
|
8
|
+
uri = URI(file_uri)
|
9
|
+
|
10
|
+
return uri.path if uri.is_a?(URI::File)
|
11
|
+
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_path(file_path)
|
16
|
+
"file://#{file_path}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|