Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ plugins:
- rubocop-rake

AllCops:
TargetRubyVersion: 3.1
TargetRubyVersion: 3.1 # 4.0 not yet afailable Tue Dec 30 15:07:23 PST 2025
NewCops: enable
Exclude:
- 'spec/fixture_files/**/*'
Expand Down
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ RUN gem update bundler

# Needed for byebug and some other gems
RUN apk update
RUN apk add curl make g++ sqlite-dev yaml-dev
# changes as of ruby 4:
# ncurses for guard
# linux-headers to build some io code - maybe to do with sockets
RUN apk add curl make g++ sqlite-dev yaml-dev linux-headers ncurses

WORKDIR /usr/local/src
RUN curl -O -L https://github.com/mateusza/SQLite-Levenshtein/archive/master.zip
Expand Down
38 changes: 28 additions & 10 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ PATH
fuzzy_match
json
ostruct
prism
sqlite3

GEM
Expand Down Expand Up @@ -45,11 +46,14 @@ GEM
coderay (1.1.3)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
debug (1.10.0)
date (3.5.1)
debug (1.11.1)
irb (~> 1.10)
reline (>= 0.3.8)
drb (2.2.1)
erb (6.0.1)
etc (1.4.5)
ffi (1.17.2)
ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-x86_64-linux-gnu)
formatador (1.1.0)
Expand All @@ -74,8 +78,9 @@ GEM
rubocop (< 2.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
io-console (0.7.1)
irb (1.13.1)
io-console (0.8.2)
irb (1.16.0)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.11.3)
Expand All @@ -87,7 +92,9 @@ GEM
logger (1.7.0)
lumberjack (1.2.10)
method_source (1.1.0)
minitest (5.25.5)
mini_portile2 (2.8.9)
minitest (6.0.1)
prism (~> 1.5)
minitest-reporters (1.7.1)
ansi
builder
Expand All @@ -103,22 +110,29 @@ GEM
parser (3.3.8.0)
ast (~> 2.4.1)
racc
prism (1.4.0)
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
prism (1.7.0)
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
psych (5.1.2)
psych (5.3.1)
date
stringio
racc (1.8.1)
rainbow (3.1.1)
rake (13.2.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rdoc (6.6.3.1)
rb-readline (0.5.5)
rdoc (7.0.3)
erb
psych (>= 4.0.0)
tsort
regexp_parser (2.10.0)
reline (0.5.10)
reline (0.6.3)
io-console (~> 0.5)
rubocop (1.75.5)
json (~> 2.3)
Expand Down Expand Up @@ -151,20 +165,23 @@ GEM
ruby-progressbar (1.13.0)
securerandom (0.4.1)
shellany (0.0.1)
sqlite3 (2.6.0)
mini_portile2 (~> 2.8.0)
sqlite3 (2.6.0-aarch64-linux-musl)
sqlite3 (2.6.0-x86_64-linux-gnu)
stringio (3.1.1)
stringio (3.2.0)
sync (0.5.0)
thor (1.4.0)
timeout (0.4.3)
tins (1.38.0)
bigdecimal
sync
tsort (0.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
unicode-emoji (4.2.0)
uri (1.0.3)

PLATFORMS
Expand All @@ -179,6 +196,7 @@ DEPENDENCIES
minitest
minitest-reporters
rake
rb-readline
rubocop (> 1.38.0)
rubocop-ast (> 1.32.0)
rubocop-minitest
Expand Down
2 changes: 1 addition & 1 deletion Guardfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"

group :red_green_refactor, halt_on_fail: true do
guard :minitest, all_after_pass: true, cli: '--guard' do
guard :minitest, all_after_pass: true, spring: 'rake test' do
# with Minitest::Unit
# watch(%r{^test/(.*)\/?test_(.*)\.rb$})
# watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ test: image
shell: image
docker run -it --rm $(LOCAL_LINK) $(PROJECT_NAME) sh

run_in_shell: image
docker run -it --rm $(LOCAL_LINK) $(PROJECT_NAME) sh -c '$(COMMAND)'

# Just to make sure it works.
server: image
docker run -it --rm $(LOCAL_LINK) $(PROJECT_NAME)
Expand Down
1 change: 1 addition & 0 deletions lib/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def write(*args)
t.string :superclass_name
t.string :path
t.string :class_type, null: false
t.text :parameters # JSON string of method parameters
end

add_index :scopes, :name
Expand Down
39 changes: 37 additions & 2 deletions lib/ruby_language_server/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,46 @@ def completion(context, context_scope, position_scopes)
{
isIncomplete: true,
items: completions.uniq.map do |word, hash|
{
item = {
label: word,
kind: COMPLETION_ITEM_KIND[hash[:type]&.to_sym]
}

# Add snippet for methods with parameters
if hash[:type] == 'method' && hash[:parameters]&.any?
item[:insertText] = generate_method_snippet(word, hash[:parameters])
item[:insertTextFormat] = 2 # Snippet format
end

item
end
}
end

private

def generate_method_snippet(method_name, parameters)
return method_name if parameters.empty?

# Build snippet with tab stops
param_snippets = []
tab_index = 1

parameters.each do |param|
param_snippets << case param['type']
when 'keyword'
# Keyword args with placeholder for value
"#{param['name']} ${#{tab_index}:value}"
else
# All other param types use the parameter name
"${#{tab_index}:#{param['name']}}"
end
tab_index += 1
end

"#{method_name}(#{param_snippets.join(', ')})"
end

def scopes_with_name(name, scopes)
return scopes.where(name:) if scopes.respond_to?(:where)

Expand Down Expand Up @@ -86,7 +116,12 @@ def scope_completions(word, scopes)
variable_words = RubyLanguageServer::ScopeData::Variable.where(scope_id: scope_ids).closest_to(word).limit(5).map { |variable| [variable.name, variable.scope] }
words = (scope_words + variable_words).to_h
good_words = FuzzyMatch.new(words.keys, threshold: 0.01).find_all(word).slice(0..10) || []
words = good_words.each_with_object({}) { |w, hash| hash[w] = {depth: words[w].depth, type: words[w].class_type} }.to_h
good_words.each_with_object({}) do |w, hash|
scope = words[w]
hash[w] = {depth: scope.depth, type: scope.class_type}
# Include parameters for methods
hash[w][:parameters] = scope.parsed_parameters if scope.method? && scope.parameters.present?
end
end
end
end
Expand Down
26 changes: 18 additions & 8 deletions lib/ruby_language_server/scope_data/scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ class Scope < Base
# attr_accessor :name # method
# attr_accessor :superclass_name # superclass name

def self.build(parent = nil, type = TYPE_ROOT, name = '', top_line = 1, column = 1)
def self.build(parent = nil, type = TYPE_ROOT, name = '', top_line = 1, column = 1, bottom_line = nil)
full_name = [parent&.full_name, name].compact.join(JoinHash[type])
create!(
parent:,
top_line:,
column:,
bottom_line:,
name:,
path: full_name,
class_type: type
Expand Down Expand Up @@ -79,14 +80,23 @@ def named_scope?
[TYPE_MODULE, TYPE_CLASS, TYPE_METHOD, TYPE_VARIABLE].include?(class_type)
end

# Called from ScopeParser when a peer of this block starts - because we don't have an end notifier.
# So we do some reasonable cleanup here.
def close(line)
return destroy! if block_scope? && variables.none?
# Get parameters as an array of hashes
def parsed_parameters
return [] unless parameters.present?

self.top_line ||= variables.map(&:top_line).min
self.bottom_line = [bottom_line, line].compact.min
save!
JSON.parse(parameters)
rescue JSON::ParserError
[]
end

# Set parameters from an array
def set_parameters(params_array)
self.parameters = params_array.to_json if params_array.present?
end

# Called from ScopeParser to cleanup empty blocks.
def close
destroy! if block_scope? && variables.none?
end

private
Expand Down
Loading