ruby_language_server 0.2.10 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10cb4b4c269544056f69b47cd3ffa4bfe55eeb2637e9d8a47279c8a13e34bb7d
4
- data.tar.gz: b17613ed4c17e3fc62cc04623ece8ca442568ae54d3843cdad1adf630521e8bc
3
+ metadata.gz: 020e4ff18a466c0ca90d360610148699d96e9d983f173499169fe79923c0b201
4
+ data.tar.gz: abc71a6aa28296af4c53404a74500690e339a9c7b27c563b8699bfedcb0f16fd
5
5
  SHA512:
6
- metadata.gz: a1d40873acea7759a42cc18408c1f7783266359820772c6ba3b34c46789caca9513da83ffd568206a3d77a3a5b36fc6bdf799ae43acb829049cdfa9ac5c50afb
7
- data.tar.gz: 494d757d57eae4824f1b1be25756a033000a75baab0ed0d3dfa3c892ea49c85225f6b9c3ce37ba1258b9cb245d70ac83e8b754ff8cafa0bff1a13e5c05e4e104
6
+ metadata.gz: 441a9f0fad64e0fef21268bc7b4e78ad7c3d966d1b690a53af404c27ae063238c94dca7455eb8ebb1b259d8aec2e3f409cfac28b83973427866f6da3e1e1e00f
7
+ data.tar.gz: 8010f23f8fd7f24cb2a9e3883fdf86cd9c9401720a005bdef97a8537844edc44b14a311dcca19937c18076efebb454093eed11947cdc4409b37bb7be578ff16c
data/CHANGELOG.txt CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ #### 0.3.0 Tue Aug 20 17:23:38 PDT 2019
4
+
5
+ * #33 - migrate to database
6
+ ** add sqlite
7
+ ** add ActiveRecord
8
+
3
9
  #### 0.2.10 Mon Aug 5 22:34:54 PDT 2019
4
10
 
5
11
  * #41 - Rubocop version mismatch
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_language_server (0.2.10)
4
+ ruby_language_server (0.3.0)
5
+ activerecord
5
6
  amatch
6
7
  bundler
7
8
  etc
@@ -9,16 +10,30 @@ PATH
9
10
  json
10
11
  rubocop
11
12
  rubocop-rspec
13
+ sqlite3
12
14
 
13
15
  GEM
14
16
  remote: https://rubygems.org/
15
17
  specs:
18
+ activemodel (5.2.3)
19
+ activesupport (= 5.2.3)
20
+ activerecord (5.2.3)
21
+ activemodel (= 5.2.3)
22
+ activesupport (= 5.2.3)
23
+ arel (>= 9.0)
24
+ activesupport (5.2.3)
25
+ concurrent-ruby (~> 1.0, >= 1.0.2)
26
+ i18n (>= 0.7, < 2)
27
+ minitest (~> 5.1)
28
+ tzinfo (~> 1.1)
16
29
  amatch (0.4.0)
17
30
  mize
18
31
  tins (~> 1.0)
32
+ arel (9.0.0)
19
33
  ast (2.4.0)
20
34
  byebug (11.0.1)
21
35
  coderay (1.1.2)
36
+ concurrent-ruby (1.1.5)
22
37
  etc (1.0.1)
23
38
  ffi (1.11.1)
24
39
  formatador (0.2.5)
@@ -39,6 +54,8 @@ GEM
39
54
  guard-rubocop (1.3.0)
40
55
  guard (~> 2.0)
41
56
  rubocop (~> 0.20)
57
+ i18n (1.6.0)
58
+ concurrent-ruby (~> 1.0)
42
59
  jaro_winkler (1.5.3)
43
60
  json (2.2.0)
44
61
  listen (3.1.5)
@@ -87,8 +104,12 @@ GEM
87
104
  sexp_processor (~> 4.9)
88
105
  sexp_processor (4.12.1)
89
106
  shellany (0.0.1)
107
+ sqlite3 (1.4.1)
90
108
  thor (0.20.3)
109
+ thread_safe (0.3.6)
91
110
  tins (1.21.1)
111
+ tzinfo (1.2.5)
112
+ thread_safe (~> 0.1)
92
113
  unicode-display_width (1.6.0)
93
114
 
94
115
  PLATFORMS
data/Makefile CHANGED
@@ -5,9 +5,12 @@ build:
5
5
  docker build -t $(PROJECT_NAME) .
6
6
 
7
7
  guard: build
8
- docker run -it $(LOCAL_LINK) $(PROJECT_NAME) bundle exec guard
8
+ echo > active_record.log
9
+ docker run -it --rm $(LOCAL_LINK) -e LOG_LEVEL=DEBUG $(PROJECT_NAME) bundle exec guard
10
+ echo > active_record.log
9
11
 
10
12
  continuous_development: build
13
+ docker build -t local_ruby_language_server .
11
14
  echo "You are going to want to set the ide-ruby 'Image Name' to local_ruby_language_server"
12
15
  sleep 15
13
16
  while (true) ; \
data/README.md CHANGED
@@ -50,7 +50,7 @@ Write tests and guard will run them. Make changes and reload the window. Test
50
50
 
51
51
  * For for release
52
52
  * bump version in version.rb file and Gemfile.lock
53
- * change log
53
+ * [CHANGELOG.txt](CHANGELOG.txt)
54
54
  * make gem_release
55
55
 
56
56
  # Authors
@@ -5,5 +5,5 @@ $LOAD_PATH << File.join(__dir__, '..', 'lib')
5
5
 
6
6
  require 'ruby_language_server'
7
7
 
8
- server = RubyLanguageServer::Server.new
9
- RubyLanguageServer::IO.new(server)
8
+ application = RubyLanguageServer::Application.new
9
+ application.start
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Base.establish_connection(
4
+ adapter: 'sqlite3',
5
+ database: 'file::memory:?cache=shared',
6
+ # database: '/database',
7
+ pool: 5, # does not seem to help
8
+ checkout_timeout: 30.seconds # does not seem to help
9
+ )
10
+
11
+ if ENV['LOG_LEVEL'] == 'DEBUG'
12
+ begin
13
+ warn('Turning on active record logging')
14
+ ActiveRecord::Base.logger = Logger.new(File.open('active_record.log', 'w'))
15
+ rescue Exception => e
16
+ ActiveRecord::Base.logger = Logger.new(STDERR)
17
+ ActiveRecord::Base.logger.error(e)
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sqlite3'
data/lib/db/schema.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define do
4
+ def write(*args)
5
+ RubyLanguageServer.logger.debug(args)
6
+ end
7
+
8
+ create_table :scopes, force: true do |t|
9
+ t.references :code_file
10
+ t.integer :parent_id
11
+ t.integer :top_line # first line
12
+ t.integer :bottom_line # last line
13
+ t.integer :column
14
+ t.string :name, default: ''
15
+ t.string :superclass_name
16
+ t.string :path
17
+ t.string :class_type, null: false
18
+ end
19
+
20
+ add_index :scopes, :name
21
+ add_index :scopes, :path
22
+ add_index :scopes, :code_file
23
+
24
+ create_table :variables, force: true do |t|
25
+ t.references :code_file
26
+ t.references :scope
27
+ t.integer :line
28
+ t.integer :column
29
+ t.string :name
30
+ t.string :path
31
+ t.string :variable_type
32
+ end
33
+
34
+ add_index :variables, :name
35
+ add_index :variables, :code_file
36
+
37
+ create_table :code_files, force: true do |t|
38
+ t.string :uri
39
+ t.boolean :refresh_root_scope, default: true
40
+ t.text :text
41
+ end
42
+
43
+ add_index :code_files, :uri
44
+ end
@@ -1,14 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'ruby_language_server/logger' # do this first!
4
- require_relative 'ruby_language_server/version'
5
- require_relative 'ruby_language_server/gem_installer'
6
- require_relative 'ruby_language_server/io'
7
- require_relative 'ruby_language_server/location'
8
- require_relative 'ruby_language_server/code_file'
9
- require_relative 'ruby_language_server/scope_parser'
10
- require_relative 'ruby_language_server/good_cop'
11
- require_relative 'ruby_language_server/project_manager'
12
- require_relative 'ruby_language_server/server'
13
- require_relative 'ruby_language_server/line_context'
14
- require_relative 'ruby_language_server/completion'
3
+ require_relative 'ruby_language_server/application'
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+
5
+ require_relative 'logger' # do this first!
6
+ require_relative '../config/initializers/sqlite'
7
+ require_relative '../config/initializers/active_record'
8
+ require_relative '../db/schema'
9
+
10
+ require_relative 'version'
11
+ require_relative 'gem_installer'
12
+ require_relative 'io'
13
+ require_relative 'location'
14
+ require_relative 'code_file'
15
+ require_relative 'scope_parser'
16
+ require_relative 'good_cop'
17
+ require_relative 'project_manager'
18
+ require_relative 'server'
19
+ require_relative 'line_context'
20
+ require_relative 'completion'
21
+
22
+ module RubyLanguageServer
23
+ class Application
24
+ def start
25
+ update_mutex = Monitor.new
26
+ server = RubyLanguageServer::Server.new(update_mutex)
27
+ RubyLanguageServer::IO.new(server, update_mutex)
28
+ end
29
+ end
30
+ end
@@ -1,32 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_record'
4
+
3
5
  require_relative 'scope_data/base'
4
6
  require_relative 'scope_data/scope'
5
7
  require_relative 'scope_data/variable'
6
8
 
7
9
  module RubyLanguageServer
8
- class CodeFile
9
- attr_reader :uri
10
- attr_reader :text
10
+ class CodeFile < ActiveRecord::Base
11
+ has_many :scopes, class_name: 'RubyLanguageServer::ScopeData::Scope', dependent: :destroy do
12
+ def root_scope
13
+ where(class_type: RubyLanguageServer::ScopeData::Scope::TYPE_ROOT).first
14
+ end
15
+ end
16
+ has_many :variables, class_name: 'RubyLanguageServer::ScopeData::Variable', dependent: :destroy
17
+
11
18
  attr_accessor :diagnostics
12
19
 
13
- def initialize(uri, text)
20
+ def self.build(uri, text)
14
21
  RubyLanguageServer.logger.debug("CodeFile initialize #{uri}")
15
- @uri = uri
16
- @text = text
17
- @refresh_root_scope = true
18
- end
19
22
 
20
- def text=(new_text)
21
- RubyLanguageServer.logger.debug("text= for #{uri}")
22
- if @text == new_text
23
- RubyLanguageServer.logger.debug('IT WAS THE SAME!!!!!!!!!!!!')
24
- return
25
- end
26
- @text = new_text
27
- @refresh_root_scope = true
23
+ new_code_file = create!(uri: uri, text: text)
24
+ new_code_file
28
25
  end
29
26
 
27
+ # def text=(new_text)
28
+ # RubyLanguageServer.logger.debug("text= for #{uri}")
29
+ # if @text == new_text
30
+ # RubyLanguageServer.logger.debug('IT WAS THE SAME!!!!!!!!!!!!')
31
+ # return
32
+ # end
33
+ # @text = new_text
34
+ # update_attribute(:refresh_root_scope, true)
35
+ # root_scope
36
+ # end
37
+ #
30
38
  SYMBOL_KIND = {
31
39
  file: 1,
32
40
  'module': 5, # 2,
@@ -53,46 +61,44 @@ module RubyLanguageServer
53
61
  def ancestor_scope_name(scope)
54
62
  return_scope = scope
55
63
  while (return_scope = return_scope.parent)
56
- return return_scope.name unless return_scope.name.nil?
64
+ return return_scope.name unless return_scope.name.nil? || return_scope.block_scope?
57
65
  end
58
66
  end
59
67
 
60
68
  def tags
61
69
  RubyLanguageServer.logger.debug("Asking about tags for #{uri}")
62
- return @tags = {} if text.nil? || text == ''
70
+ @tags ||= [{}]
71
+ return @tags if text.nil?
72
+ return @tags = [{}] if text == ''
73
+
74
+ refresh_scopes_if_needed # cause root scope to reset
75
+ return @tags if scopes.reload.count <= 1 # just the root
63
76
 
64
- tags = []
65
- root_scope.self_and_descendants.each do |scope|
66
- next if scope.type == ScopeData::Base::TYPE_BLOCK
77
+ tags = scopes.reload.map do |scope|
78
+ next if scope.class_type == ScopeData::Base::TYPE_BLOCK
79
+ next if scope.root_scope?
67
80
 
68
- name = scope.name
69
- kind = SYMBOL_KIND[scope.type] || 7
70
- kind = 9 if name == 'initialize' # Magical special case
81
+ kind = SYMBOL_KIND[scope.class_type.to_sym] || 7
82
+ kind = 9 if scope.name == 'initialize' # Magical special case
71
83
  scope_hash = {
72
- name: name,
84
+ name: scope.name,
73
85
  kind: kind,
74
86
  location: Location.hash(uri, scope.top_line)
75
87
  }
76
88
  container_name = ancestor_scope_name(scope)
77
- scope_hash[:containerName] = container_name if container_name
78
- tags << scope_hash
79
-
80
- scope.variables.each do |variable|
81
- name = variable.name
82
- # We only care about counstants
83
- next unless name =~ /^[A-Z]/
84
-
85
- variable_hash = {
86
- name: name,
87
- kind: SYMBOL_KIND[:constant],
88
- location: Location.hash(uri, variable.line),
89
- containerName: scope.name
90
- }
91
- tags << variable_hash
92
- end
89
+ scope_hash[:containerName] = container_name unless container_name.blank?
90
+ scope_hash
93
91
  end
94
- # byebug
95
- tags.reject! { |tag| tag[:name].nil? }
92
+ tags += variables.constant_variables.reload.map do |variable|
93
+ name = variable.name
94
+ {
95
+ name: name,
96
+ kind: SYMBOL_KIND[:constant],
97
+ location: Location.hash(uri, variable.line - 1),
98
+ containerName: variable.scope.name
99
+ }
100
+ end
101
+ tags = tags.compact.reject { |tag| tag[:name].nil? || tag[:name] == RubyLanguageServer::ScopeData::Scope::TYPE_BLOCK }
96
102
  # RubyLanguageServer.logger.debug("Raw tags for #{uri}: #{tags}")
97
103
  # If you don't reverse the list then atom? won't be able to find the
98
104
  # container and containers will get duplicated.
@@ -106,18 +112,32 @@ module RubyLanguageServer
106
112
  @tags
107
113
  end
108
114
 
109
- def root_scope
110
- # RubyLanguageServer.logger.error("Asking about root_scope with #{text}")
111
- if @refresh_root_scope
112
- new_root_scope = ScopeParser.new(text).root_scope
113
- @root_scope ||= new_root_scope # In case we had NONE
114
- return @root_scope if new_root_scope.children.empty?
115
+ def update_text(new_text)
116
+ RubyLanguageServer.logger.debug("update_text for #{uri}")
117
+ return true if new_text == text
118
+
119
+ RubyLanguageServer.logger.debug('Changed!')
120
+ update(text: new_text, refresh_root_scope: true)
121
+ end
122
+
123
+ def refresh_scopes_if_needed
124
+ return unless refresh_root_scope
115
125
 
116
- @root_scope = new_root_scope
117
- @refresh_root_scope = false
118
- @tags = nil
126
+ RubyLanguageServer.logger.debug("Asking about root_scope for #{uri}")
127
+ RubyLanguageServer::ScopeData::Variable.where(code_file_id: self).scoping do
128
+ RubyLanguageServer::ScopeData::Scope.where(code_file_id: self).scoping do
129
+ self.class.transaction do
130
+ scopes.clear
131
+ variables.clear
132
+ new_root = ScopeParser.new(text).root_scope
133
+ RubyLanguageServer.logger.debug("new_root.children #{new_root.children.as_json}") if new_root&.children
134
+ raise ActiveRecord::Rollback if new_root.nil? || new_root.children.blank?
135
+
136
+ update_attribute(:refresh_root_scope, false)
137
+ new_root
138
+ end
139
+ end
119
140
  end
120
- @root_scope
121
141
  end
122
142
 
123
143
  # Returns the context of what is being typed in the given line
@@ -25,10 +25,10 @@ module RubyLanguageServer
25
25
 
26
26
  class << self
27
27
  def completion(context, context_scope, scopes)
28
- RubyLanguageServer.logger.debug("completion(#{context}, #{context_scope.self_and_ancestors.map(&:name)}, #{scopes.map(&:name)})")
28
+ RubyLanguageServer.logger.debug("completion(#{context}, #{scopes.map(&:name)})")
29
29
  completions =
30
30
  if context.length < 2
31
- scope_completions(context.last, context_scope.self_and_ancestors)
31
+ scope_completions(context.last, scopes)
32
32
  else
33
33
  scope_completions_in_target_context(context, context_scope, scopes)
34
34
  end
@@ -44,12 +44,13 @@ module RubyLanguageServer
44
44
  end
45
45
 
46
46
  def scope_with_name(name, scopes)
47
+ return scopes.where(name: name).first if scopes.respond_to?(:where)
48
+
47
49
  scopes.detect { |scope| scope.name == name }
48
50
  end
49
51
 
50
52
  def scope_completions_in_target_context(context, context_scope, scopes)
51
- working_array = context.dup
52
- context_word = working_array[-2]
53
+ context_word = context[-2]
53
54
  if context_word.match?(/^[A-Z]/)
54
55
  scope = scope_with_name(context_word, scopes)
55
56
  else
@@ -59,22 +60,22 @@ module RubyLanguageServer
59
60
  end
60
61
  scope ||= context_scope
61
62
  RubyLanguageServer.logger.debug("scope: #{scope}")
62
- scope_completions(context.last, scope.self_and_ancestors)
63
+ scope_completions(context.last, [scope] + scopes.includes(:variables))
63
64
  end
64
65
 
65
66
  def scope_completions(word, scopes)
66
67
  words = {}
67
68
  scopes.each_with_object(words) do |scope, words_hash|
68
- scope.children.select(&:'method?').each do |method_scope|
69
+ scope.children.method_scopes.each do |method_scope|
69
70
  words_hash[method_scope.name] ||= {
70
71
  depth: scope.depth,
71
- type: method_scope.type
72
+ type: method_scope.class_type
72
73
  }
73
74
  end
74
75
  scope.variables.each do |variable|
75
76
  words_hash[variable.name] ||= {
76
77
  depth: scope.depth,
77
- type: variable.type
78
+ type: variable.variable_type
78
79
  }
79
80
  end
80
81
  end
@@ -4,8 +4,9 @@ require 'json'
4
4
 
5
5
  module RubyLanguageServer
6
6
  class IO
7
- def initialize(server)
7
+ def initialize(server, mutex)
8
8
  @server = server
9
+ @mutex = mutex
9
10
  server.io = self
10
11
  loop do
11
12
  (id, response) = process_request(STDIN)
@@ -57,7 +58,11 @@ module RubyLanguageServer
57
58
  params = request_json['params']
58
59
  method_name = "on_#{method_name.gsub(/[^\w]/, '_')}"
59
60
  if @server.respond_to? method_name
60
- response = @server.send(method_name, params)
61
+ RubyLanguageServer.logger.debug 'Locking io'
62
+ response = @mutex.synchronize do
63
+ @server.send(method_name, params)
64
+ end
65
+ RubyLanguageServer.logger.debug 'UNLocking io'
61
66
  exit(true) if response == 'EXIT'
62
67
  return id, response
63
68
  else
@@ -3,14 +3,14 @@
3
3
  module RubyLanguageServer
4
4
  # Hash factories for the language server
5
5
  module Location
6
- def self.hash(uri, start_line, start_character = 1, end_line = nil, end_character = nil)
6
+ def self.hash(uri, start_line, start_character = 0, end_line = nil, end_character = nil)
7
7
  {
8
8
  uri: uri,
9
9
  range: position_hash(start_line, start_character, end_line, end_character)
10
10
  }
11
11
  end
12
12
 
13
- def self.position_hash(start_line, start_character = 1, end_line = nil, end_character = nil)
13
+ def self.position_hash(start_line, start_character = 0, end_line = nil, end_character = nil)
14
14
  end_line ||= start_line
15
15
  end_character ||= start_character
16
16
  {
@@ -49,13 +49,8 @@ module RubyLanguageServer
49
49
 
50
50
  @root_uri = "file://#{path}"
51
51
  # This is {uri: code_file} where content stuff is like
52
- @uri_code_file_hash = {}
53
- @update_mutext = Mutex.new
54
-
55
52
  @additional_gems_installed = false
56
53
  @additional_gem_mutex = Mutex.new
57
-
58
- scan_all_project_files
59
54
  end
60
55
 
61
56
  def diagnostics_ready?
@@ -90,13 +85,14 @@ module RubyLanguageServer
90
85
  end
91
86
 
92
87
  def all_scopes
93
- @uri_code_file_hash.values.map(&:root_scope).map(&:self_and_descendants).flatten
88
+ RubyLanguageServer::ScopeData::Scope.all
94
89
  end
95
90
 
96
91
  # Return the list of scopes [deepest, parent, ..., Object]
97
92
  def scopes_at(uri, position)
98
- root_scope = root_scope_for(uri)
99
- root_scope.scopes_at(position)
93
+ code_file = code_file_for_uri(uri)
94
+ code_file.refresh_scopes_if_needed
95
+ code_file.scopes.for_line(position.line).where.not(path: nil).by_path_length
100
96
  end
101
97
 
102
98
  def completion_at(uri, position)
@@ -104,11 +100,12 @@ module RubyLanguageServer
104
100
  relative_position.character = relative_position.character # To get before the . or ::
105
101
  # RubyLanguageServer.logger.debug("relative_position #{relative_position}")
106
102
  RubyLanguageServer.logger.debug("scopes_at(uri, position) #{scopes_at(uri, position).map(&:name)}")
107
- context_scope = scopes_at(uri, position).first || root_scope_for(uri)
103
+ position_scopes = scopes_at(uri, position) || RubyLanguageServer::ScopeData::Scope.where(id: root_scope_for(uri).id)
104
+ context_scope = position_scopes.first
108
105
  context = context_at_location(uri, relative_position)
109
106
  return {} if context.nil? || context == ''
110
107
 
111
- RubyLanguageServer::Completion.completion(context, context_scope, all_scopes)
108
+ RubyLanguageServer::Completion.completion(context, context_scope, position_scopes)
112
109
  end
113
110
 
114
111
  # interface CompletionItem {
@@ -185,29 +182,37 @@ module RubyLanguageServer
185
182
  # data?: any
186
183
  # }
187
184
 
188
- def scan_all_project_files
185
+ def scan_all_project_files(mutex)
189
186
  project_ruby_files = Dir.glob("#{self.class.root_path}**/*.rb")
190
187
  Thread.new do
188
+ RubyLanguageServer.logger.error('Threading up!')
191
189
  project_ruby_files.each do |container_path|
190
+ # Let's not preload spec/test or vendor - yet..
191
+ next if container_path.match?(/^(.?spec|test|vendor)/)
192
+
192
193
  text = File.read(container_path)
193
194
  relative_path = container_path.delete_prefix(self.class.root_path)
194
195
  host_uri = @root_uri + relative_path
195
- update_document_content(host_uri, text)
196
+ RubyLanguageServer.logger.debug "Locking scan for #{container_path}"
197
+ mutex.synchronize do
198
+ RubyLanguageServer.logger.debug("Threading #{host_uri}")
199
+ update_document_content(host_uri, text)
200
+ code_file_for_uri(host_uri).refresh_scopes_if_needed
201
+ end
202
+ RubyLanguageServer.logger.debug "Unlocking scan for #{container_path}"
196
203
  end
197
204
  end
198
205
  end
199
206
 
200
207
  # returns diagnostic info (if possible)
201
208
  def update_document_content(uri, text)
202
- @update_mutext.synchronize do
203
- RubyLanguageServer.logger.debug("update_document_content: #{uri}")
204
- # RubyLanguageServer.logger.error("@root_path: #{@root_path}")
205
- code_file = code_file_for_uri(uri)
206
- return code_file.diagnostics if code_file.text == text
207
-
208
- code_file.text = text
209
- diagnostics_ready? ? updated_diagnostics_for_codefile(code_file) : []
210
- end
209
+ RubyLanguageServer.logger.debug("update_document_content: #{uri}")
210
+ # RubyLanguageServer.logger.error("@root_path: #{@root_path}")
211
+ code_file = code_file_for_uri(uri)
212
+ return code_file.diagnostics if code_file.text == text
213
+
214
+ code_file.update_text(text)
215
+ diagnostics_ready? ? updated_diagnostics_for_codefile(code_file) : []
211
216
  end
212
217
 
213
218
  def updated_diagnostics_for_codefile(code_file)
@@ -224,19 +229,19 @@ module RubyLanguageServer
224
229
  end
225
230
 
226
231
  def word_at_location(uri, position)
227
- context_at_location(uri, position).last
232
+ context_at_location(uri, position)&.last
228
233
  end
229
234
 
230
235
  def possible_definitions(uri, position)
231
236
  name = word_at_location(uri, position)
232
- return {} if name == ''
237
+ return {} if name.blank?
233
238
 
234
239
  name = 'initialize' if name == 'new'
235
240
  scope = scopes_at(uri, position).first
236
241
  results = scope_definitions_for(name, scope, uri)
237
242
  return results unless results.empty?
238
243
 
239
- project_definitions_for(name, scope)
244
+ project_definitions_for(name)
240
245
  end
241
246
 
242
247
  def scope_definitions_for(name, scope, uri)
@@ -244,7 +249,7 @@ module RubyLanguageServer
244
249
  return_array = []
245
250
  while check_scope
246
251
  scope.variables.each do |variable|
247
- return_array << Location.hash(uri, variable.line) if variable.name == name
252
+ return_array << Location.hash(uri.delete_prefix(self.class.root_uri), variable.line - 1) if variable.name == name
248
253
  end
249
254
  check_scope = check_scope.parent
250
255
  end
@@ -252,25 +257,18 @@ module RubyLanguageServer
252
257
  return_array.uniq
253
258
  end
254
259
 
255
- def project_definitions_for(name, _scope)
256
- return_array = @uri_code_file_hash.keys.each_with_object([]) do |uri, ary|
257
- tags = tags_for_uri(uri)
258
- RubyLanguageServer.logger.debug("tags_for_uri(#{uri}): #{tags_for_uri(uri)}")
259
- next if tags.nil?
260
-
261
- match_tags = tags.select { |tag| tag[:name] == name }
262
- match_tags.each do |tag|
263
- ary << Location.hash(uri, tag[:location][:range][:start][:line] + 1)
264
- end
260
+ def project_definitions_for(name)
261
+ scopes = RubyLanguageServer::ScopeData::Scope.where(name: name)
262
+ variables = RubyLanguageServer::ScopeData::Variable.constant_variables.where(name: name)
263
+ (scopes + variables).map do |thing|
264
+ Location.hash(thing.code_file.uri.delete_prefix(self.class.root_uri), thing.top_line)
265
265
  end
266
- return_array
267
266
  end
268
267
 
269
268
  private
270
269
 
271
270
  def code_file_for_uri(uri)
272
- code_file = @uri_code_file_hash[uri]
273
- code_file = @uri_code_file_hash[uri] = CodeFile.new(uri, nil) if code_file.nil?
271
+ code_file = CodeFile.find_by_uri(uri) || CodeFile.build(uri, nil)
274
272
  code_file
275
273
  end
276
274
 
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_record'
4
+
3
5
  module RubyLanguageServer
4
6
  module ScopeData
5
- class Base
6
- TYPE_MODULE = :module
7
- TYPE_CLASS = :class
8
- TYPE_METHOD = :method
9
- TYPE_BLOCK = :block
10
- TYPE_ROOT = :root
11
- TYPE_VARIABLE = :variable
7
+ class Base < ActiveRecord::Base
8
+ self.abstract_class = true
9
+
10
+ TYPE_MODULE = 'module'
11
+ TYPE_CLASS = 'class'
12
+ TYPE_METHOD = 'method'
13
+ TYPE_BLOCK = 'block'
14
+ TYPE_ROOT = 'root'
15
+ TYPE_VARIABLE = 'variable'
16
+
17
+ BLOCK_NAME = 'block'
12
18
 
13
19
  JoinHash = {
14
20
  TYPE_MODULE => '::',
@@ -1,103 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_record'
4
+
3
5
  module RubyLanguageServer
4
6
  module ScopeData
5
7
  # The Scope class is basically a container with context.
6
8
  # It is used to track top & bottom line, variables in this scope, constants, and children - which could be functions, classes, blocks, etc. Anything that adds scope.
7
9
  class Scope < Base
8
- include Enumerable
9
-
10
- attr_accessor :top_line # first line
11
- attr_accessor :bottom_line # last line
12
- attr_accessor :depth # how many parent scopes
13
- attr_accessor :parent # parent scope
14
- attr_accessor :variables # variables declared in this scope
15
- attr_accessor :constants # constants declared in this scope
16
- attr_accessor :children # child scopes
17
- attr_accessor :name # method
18
- attr_accessor :superclass_name # superclass name
19
-
20
- def initialize(parent = nil, type = TYPE_ROOT, name = '', top_line = 1, _column = 1)
21
- super()
22
- @parent = parent
23
- @type = type
24
- @name = name
25
- @top_line = top_line
26
- @depth = parent.nil? ? 0 : parent.depth + 1
27
- if type == TYPE_ROOT
28
- @full_name = nil
29
- else
30
- @full_name = [parent ? parent.full_name : nil, @name].compact.join(JoinHash[type])
31
- end
32
- @children = []
33
- @variables = []
34
- @constants = []
35
- end
10
+ has_many :variables, dependent: :destroy
11
+ belongs_to :code_file
12
+ belongs_to :parent, class_name: 'Scope', optional: true
13
+ has_many :children, class_name: 'Scope', foreign_key: :parent_id
36
14
 
37
- def inspect
38
- "Scope: #{@name} (#{@full_name} - #{@type}) #{@top_line}-#{@bottom_line} children: #{@children} vars: #{@variables}"
39
- end
15
+ scope :method_scopes, -> { where(class_type: TYPE_METHOD) }
16
+ scope :for_line, ->(line) { where('top_line <= ? AND bottom_line >= ?', line, line).or(where(parent_id: nil)) }
17
+ scope :by_path_length, -> { order('length(path) DESC') }
18
+ # attr_accessor :top_line # first line
19
+ # attr_accessor :bottom_line # last line
20
+ # attr_accessor :parent # parent scope
21
+ # attr_accessor :constants # constants declared in this scope
22
+ # attr_accessor :name # method
23
+ # attr_accessor :superclass_name # superclass name
40
24
 
41
- def pretty_print(pp) # rubocop:disable Naming/UncommunicativeMethodParamName
42
- {
43
- Scope: {
44
- type: type,
45
- name: name,
46
- lines: [@top_line, @bottom_line],
47
- children: children,
48
- variables: variables
49
- }
50
- }.pretty_print(pp)
25
+ def self.build(parent = nil, type = TYPE_ROOT, name = '', top_line = 1, column = 1)
26
+ full_name = [parent ? parent.full_name : nil, name].compact.join(JoinHash[type])
27
+ create!(
28
+ parent: parent,
29
+ top_line: top_line,
30
+ column: column,
31
+ name: name,
32
+ path: full_name,
33
+ class_type: type
34
+ )
51
35
  end
52
36
 
53
37
  def full_name
54
- @full_name || @name
38
+ path # @full_name || @name
55
39
  end
56
40
 
57
- def has_variable_or_constant?(variable) # rubocop:disable Naming/PredicateName
58
- test_array = variable.constant? ? constants : variables
59
- matching_variable = test_array.detect { |test_variable| (test_variable.name == variable.name) }
60
- !matching_variable.nil?
41
+ def depth
42
+ return 0 if path.blank?
43
+
44
+ scope_parts.count
61
45
  end
62
46
 
63
47
  # Return the deepest child scopes of this scope - and on up.
64
48
  # Not done recuresively because we don't really need to.
65
49
  # Normally called on a root scope.
66
- def scopes_at(position)
67
- line = position.line
68
- matching_scopes = select do |scope|
69
- scope.top_line && scope.bottom_line && (scope.top_line..scope.bottom_line).cover?(line)
70
- end
71
- return [] if matching_scopes == []
72
-
73
- deepest_scope = matching_scopes.max_by(&:depth)
74
- deepest_scope.self_and_ancestors
75
- end
76
-
77
- def each
78
- self_and_descendants.each { |member| yield member }
79
- end
50
+ # def scopes_at(position)
51
+ # line = position.line
52
+ # matching_scopes = self_and_descendants.where('top_line <= ?', line).where('bottom_line >= ?', line)
53
+ # deepest_scope = matching_scopes.max_by(&:depth)
54
+ # deepest_scope&.self_and_ancestors || []
55
+ # end
80
56
 
81
57
  # Self and all descendents flattened into array
82
58
  def self_and_descendants
83
- [self] + descendants
59
+ return Scope.all if root_scope?
60
+
61
+ Scope.where('path like ?', "#{path}%")
84
62
  end
85
63
 
86
64
  def descendants
87
- children.map(&:self_and_descendants).flatten
65
+ Scope.where('path like ?', "#{path}_%")
88
66
  end
89
67
 
90
68
  # [self, parent, parent.parent...]
91
- def self_and_ancestors
92
- [self, parent&.self_and_ancestors].flatten.compact
93
- end
69
+ # def self_and_ancestors
70
+ # return [self] if path.blank?
71
+ # remaining_path = path.dup
72
+ # ancestor_paths = scope_parts.inject([]) do |ary, scope_part|
73
+ # ary << remaining_path
74
+ # remaining_path =
75
+ # ary
76
+ # end
77
+ # [self, parent&.self_and_ancestors].flatten.compact
78
+ # end
94
79
 
95
80
  def set_superclass_name(partial)
96
81
  if partial.start_with?('::')
97
- @superclass_name = partial.gsub(/^::/, '')
82
+ self.superclass_name = partial.gsub(/^::/, '')
98
83
  else
99
- @superclass_name = [parent ? parent.full_name : nil, partial].compact.join(JoinHash[type])
84
+ self.superclass_name = [parent ? parent.full_name : nil, partial].compact.join(JoinHash[class_type])
100
85
  end
86
+ save!
87
+ end
88
+
89
+ def root_scope?
90
+ class_type == TYPE_ROOT
91
+ end
92
+
93
+ def block_scope?
94
+ class_type == TYPE_BLOCK
95
+ end
96
+
97
+ private
98
+
99
+ def scope_parts
100
+ path&.split(/#{JoinHash.values.reject(&:blank?).uniq.join('|')}/)
101
101
  end
102
102
  end
103
103
  end
@@ -1,24 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_record'
4
+
3
5
  module RubyLanguageServer
4
6
  module ScopeData
5
7
  class Variable < Base
6
- attr_accessor :line # line
7
- attr_accessor :column # column
8
- attr_accessor :name # name
9
- attr_accessor :full_name # Module::Class name
8
+ belongs_to :code_file
9
+ belongs_to :scope
10
+
11
+ scope :constant_variables, -> { where("SUBSTR(name, 1, 1) between ('A') and ('Z')") }
12
+
13
+ # attr_accessor :line # line
14
+ # attr_accessor :column # column
15
+ # attr_accessor :name # name
16
+ # attr_accessor :path # Module::Class name
10
17
 
11
- def initialize(scope, name, line = 1, column = 1, type = TYPE_VARIABLE)
12
- @name = name
13
- @line = line
14
- @column = column
15
- @full_name = [scope.full_name, @name].join(JoinHash[TYPE_VARIABLE])
16
- @type = type
17
- raise "bogus variable #{inspect}" unless @name.instance_of? String
18
+ def self.build(scope, name, line = 1, column = 1, type = TYPE_VARIABLE)
19
+ path = [scope.full_name, name].join(JoinHash[TYPE_VARIABLE])
20
+ create!(
21
+ line: line,
22
+ column: column,
23
+ name: name,
24
+ path: path,
25
+ variable_type: type
26
+ )
27
+ # @name = name
28
+ # @line = line
29
+ # @column = column
30
+ # @full_name =
31
+ # @type = type
32
+ # raise "bogus variable #{inspect}" unless @name.instance_of? String
18
33
  end
19
34
 
20
35
  def constant?
21
- !@name&.match(/^[A-Z]/).nil?
36
+ !name&.match(/^[A-Z]/).nil?
37
+ end
38
+
39
+ # Convenience for tags
40
+ def top_line
41
+ line
22
42
  end
23
43
  end
24
44
  end
@@ -28,7 +28,7 @@ module RubyLanguageServer
28
28
  def root_scope
29
29
  return @root_scope unless @root_scope.nil?
30
30
 
31
- @root_scope = new_root_scope
31
+ @root_scope = ScopeData::Scope.where(path: nil, class_type: ScopeData::Scope::TYPE_ROOT).first_or_create!
32
32
  @current_scope = @root_scope
33
33
  process(@sexp)
34
34
  @root_scope
@@ -117,13 +117,14 @@ module RubyLanguageServer
117
117
  # add_scope(args, rest, ScopeData::Scope::TYPE_BLOCK)
118
118
  unless @current_scope == scope
119
119
  scope.bottom_line = [scope&.bottom_line, @current_scope.bottom_line].compact.max
120
+ scope.save!
120
121
  pop_scope
121
122
  end
122
123
  end
123
124
 
124
125
  def on_do_block(args, rest)
125
126
  ((_, ((_, (_, (_, _name, (line, column))))))) = args
126
- push_scope(ScopeData::Scope::TYPE_BLOCK, nil, line, column, false)
127
+ push_scope(ScopeData::Scope::TYPE_BLOCK, 'block', line, column, false)
127
128
  process(args)
128
129
  process(rest)
129
130
  pop_scope
@@ -268,17 +269,22 @@ module RubyLanguageServer
268
269
  private
269
270
 
270
271
  def add_variable(name, line, column, scope = @current_scope)
271
- new_variable = ScopeData::Variable.new(scope, name, line, column)
272
- # blocks don't declare their first line in the parser
273
- scope.top_line ||= line
274
- scope.variables << new_variable unless scope.has_variable_or_constant?(new_variable)
272
+ scope.variables.where(name: name).first_or_create(line: line, column: column)
273
+ if scope.top_line.blank?
274
+ scope.top_line = line
275
+ scope.save!
276
+ end
277
+ # new_variable = ScopeData::Variable.build(scope, name, line, column)
278
+ # # blocks don't declare their first line in the parser
279
+ # scope.top_line ||= line
280
+ # scope.variables << new_variable unless scope.has_variable_or_constant?(new_variable)
275
281
  end
276
282
 
277
283
  def add_ivar(name, line, column)
278
284
  scope = @current_scope
279
285
  unless scope == root_scope
280
286
  ivar_scope_types = [ScopeData::Base::TYPE_CLASS, ScopeData::Base::TYPE_MODULE]
281
- while !ivar_scope_types.include?(scope.type) && !scope.parent.nil?
287
+ while !ivar_scope_types.include?(scope.class_type) && !scope.parent.nil?
282
288
  scope = scope.parent
283
289
  end
284
290
  end
@@ -299,20 +305,9 @@ module RubyLanguageServer
299
305
 
300
306
  def push_scope(type, name, top_line, column, close_siblings = true)
301
307
  close_sibling_scopes(top_line) if close_siblings
302
- # The default root scope is Object. Which is fine if we're adding methods.
303
- # But if we're adding a class, we don't care that it's in Object.
304
- new_scope =
305
- if (type_is_class_or_module(type) && (@current_scope == root_scope))
306
- ScopeData::Scope.new(nil, type, name, top_line, column)
307
- else
308
- ScopeData::Scope.new(@current_scope, type, name, top_line, column)
309
- end
308
+ new_scope = ScopeData::Scope.build(@current_scope, type, name, top_line, column)
310
309
  new_scope.bottom_line = @lines
311
- if new_scope.parent.nil? # was it a class or module at the root
312
- root_scope.children << new_scope
313
- else
314
- @current_scope.children << new_scope
315
- end
310
+ new_scope.save!
316
311
  @current_scope = new_scope
317
312
  end
318
313
 
@@ -320,19 +315,15 @@ module RubyLanguageServer
320
315
  # The notion is that when you start the next scope, all the previous peers and unclosed descendents of the previous peer should be closed.
321
316
  def close_sibling_scopes(line)
322
317
  parent_scope = @current_scope
323
- parent_scope&.descendants&.each { |scope| scope.bottom_line = [scope.bottom_line, line - 1].compact.min }
318
+ parent_scope&.descendants&.each do |scope|
319
+ scope.bottom_line = [scope.bottom_line, line - 1].compact.min
320
+ scope.save!
321
+ end
324
322
  end
325
323
 
326
324
  def pop_scope
327
325
  @current_scope = @current_scope.parent || root_scope # in case we are leaving a root class/module
328
326
  end
329
-
330
- def new_root_scope
331
- ScopeData::Scope.new.tap do |scope|
332
- scope.type = ScopeData::Scope::TYPE_ROOT
333
- scope.name = nil
334
- end
335
- end
336
327
  end
337
328
 
338
329
  # This class builds on Ripper's sexp processor to add ruby and rails magic.
@@ -7,11 +7,17 @@ module RubyLanguageServer
7
7
  class Server
8
8
  attr_accessor :io
9
9
 
10
+ def initialize(mutex)
11
+ @mutex = mutex
12
+ end
13
+
10
14
  def on_initialize(params)
11
15
  RubyLanguageServer.logger.info("on_initialize: #{params}")
16
+ RubyLanguageServer::CodeFile.all # Just to warm up active_record.
12
17
  root_path = params['rootPath']
13
18
  root_uri = params['rootUri']
14
19
  @project_manager = ProjectManager.new(root_path, root_uri)
20
+ @project_manager.scan_all_project_files(@mutex)
15
21
  gem_string = ENV.fetch('ADDITIONAL_GEMS') {}
16
22
  gem_array = (gem_string.split(',').compact.map(&:strip).reject { |string| string == '' } if gem_string && !gem_string.empty?)
17
23
  @project_manager.install_additional_gems(gem_array)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLanguageServer
4
- VERSION = '0.2.10'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -45,6 +45,9 @@ Gem::Specification.new do |spec|
45
45
  spec.add_dependency 'amatch' # in c
46
46
  spec.add_dependency 'fuzzy_match' # completion matching
47
47
 
48
+ spec.add_dependency 'activerecord'
49
+ spec.add_dependency 'sqlite3'
50
+
48
51
  spec.add_development_dependency 'guard'
49
52
  spec.add_development_dependency 'guard-minitest'
50
53
  spec.add_development_dependency 'guard-rubocop'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_language_server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.10
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kurt Werle
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-06 00:00:00.000000000 Z
11
+ date: 2019-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,6 +108,34 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activerecord
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: sqlite3
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
111
139
  - !ruby/object:Gem::Dependency
112
140
  name: guard
113
141
  requirement: !ruby/object:Gem::Requirement
@@ -256,8 +284,12 @@ files:
256
284
  - bin/console
257
285
  - bin/setup
258
286
  - exe/ruby_language_server
287
+ - lib/config/initializers/active_record.rb
288
+ - lib/config/initializers/sqlite.rb
289
+ - lib/db/schema.rb
259
290
  - lib/resources/fallback_rubocop.yml
260
291
  - lib/ruby_language_server.rb
292
+ - lib/ruby_language_server/application.rb
261
293
  - lib/ruby_language_server/code_file.rb
262
294
  - lib/ruby_language_server/completion.rb
263
295
  - lib/ruby_language_server/gem_installer.rb