ruby_language_server 0.2.10 → 0.3.0

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