@@ -241,6 +241,100 @@ def list
241241 puts "#{ workspace . filenames . length } files total."
242242 end
243243
244+ desc 'profile [FILE]' , 'Profile go-to-definition performance using vernier'
245+ option :directory , type : :string , aliases : :d , desc : 'The workspace directory' , default : '.'
246+ option :output_dir , type : :string , aliases : :o , desc : 'The output directory for profiles' , default : './tmp/profiles'
247+ option :line , type : :numeric , aliases : :l , desc : 'Line number (0-based)' , default : 4
248+ option :column , type : :numeric , aliases : :c , desc : 'Column number' , default : 10
249+ # @param file [String, nil]
250+ # @return [void]
251+ def profile ( file = nil ) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
252+ begin
253+ require 'vernier'
254+ rescue LoadError
255+ STDERR . puts "vernier gem not found. Install with: gem install vernier"
256+ return
257+ end
258+
259+ directory = File . realpath ( options [ :directory ] )
260+ FileUtils . mkdir_p ( options [ :output_dir ] )
261+
262+ host = Solargraph ::LanguageServer ::Host . new
263+ host . client_capabilities . merge! ( { 'window' => { 'workDoneProgress' => true } } )
264+ def host . send_notification method , params
265+ puts "Notification: #{ method } - #{ params } "
266+ end
267+
268+ puts "Parsing and mapping source files..."
269+ prepare_start = Time . now
270+ Vernier . profile ( out : "#{ options [ :output_dir ] } /parse_benchmark.json.gz" ) do
271+ puts "Mapping libraries"
272+ host . prepare ( directory )
273+ sleep 0.2 until host . libraries . all? ( &:mapped? )
274+ end
275+ prepare_time = Time . now - prepare_start
276+
277+ puts "Building the catalog..."
278+ catalog_start = Time . now
279+ Vernier . profile ( out : "#{ options [ :output_dir ] } /catalog_benchmark.json.gz" ) do
280+ host . catalog
281+ end
282+ catalog_time = Time . now - catalog_start
283+
284+ # Determine test file
285+ if file
286+ test_file = File . join ( directory , file )
287+ else
288+ test_file = File . join ( directory , 'lib' , 'other.rb' )
289+ unless File . exist? ( test_file )
290+ # Fallback to any Ruby file in the workspace
291+ workspace = Solargraph ::Workspace . new ( directory )
292+ test_file = workspace . filenames . find { |f | f . end_with? ( '.rb' ) }
293+ unless test_file
294+ STDERR . puts "No Ruby files found in workspace"
295+ return
296+ end
297+ end
298+ end
299+
300+ file_uri = Solargraph ::LanguageServer ::UriHelpers . file_to_uri ( File . absolute_path ( test_file ) )
301+
302+ puts "Profiling go-to-definition for #{ test_file } "
303+ puts "Position: line #{ options [ :line ] } , column #{ options [ :column ] } "
304+
305+ definition_start = Time . now
306+ Vernier . profile ( out : "#{ options [ :output_dir ] } /definition_benchmark.json.gz" ) do
307+ message = Solargraph ::LanguageServer ::Message ::TextDocument ::Definition . new (
308+ host , {
309+ 'params' => {
310+ 'textDocument' => { 'uri' => file_uri } ,
311+ 'position' => { 'line' => options [ :line ] , 'character' => options [ :column ] }
312+ }
313+ }
314+ )
315+ puts "Processing go-to-definition request..."
316+ result = message . process
317+
318+ puts "Result: #{ result . inspect } "
319+ end
320+ definition_time = Time . now - definition_start
321+
322+ puts "\n === Timing Results ==="
323+ puts "Parsing & mapping: #{ ( prepare_time * 1000 ) . round ( 2 ) } ms"
324+ puts "Catalog building: #{ ( catalog_time * 1000 ) . round ( 2 ) } ms"
325+ puts "Go-to-definition: #{ ( definition_time * 1000 ) . round ( 2 ) } ms"
326+ total_time = prepare_time + catalog_time + definition_time
327+ puts "Total time: #{ ( total_time * 1000 ) . round ( 2 ) } ms"
328+
329+ puts "\n Profiles saved to:"
330+ puts " - #{ File . expand_path ( 'parse_benchmark.json.gz' , options [ :output_dir ] ) } "
331+ puts " - #{ File . expand_path ( 'catalog_benchmark.json.gz' , options [ :output_dir ] ) } "
332+ puts " - #{ File . expand_path ( 'definition_benchmark.json.gz' , options [ :output_dir ] ) } "
333+
334+ puts "\n Upload the JSON files to https://vernier.prof/ to view the profiles."
335+ puts "Or use https://rubygems.org/gems/profile-viewer to view them locally."
336+ end
337+
244338 private
245339
246340 # @param pin [Solargraph::Pin::Base]
0 commit comments