diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 58cdba509..d30ef83fd 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -135,8 +135,13 @@ def catalog bench # @return [Array] def process_macros macro_pins = [] + Solargraph.logger.debug { "ApiMap#process_macros: processing macros for #{source_maps.size} source maps" } + Solargraph.logger.debug { "ApiMap#process_macros: store has #{store.macro_method_name_pins.size} macro method name pins" } + Solargraph.logger.debug { "ApiMap#process_macros: named macros: #{store.named_macros.keys.join(', ')}" } source_maps.each do |source_map| - source_map.macro_method_candidates(store.macro_method_names).each do |node| + method_candidates = source_map.macro_method_candidates(store.macro_method_names) + Solargraph.logger.debug { "ApiMap#process_macros: processing source map for #{source_map.filename} with #{method_candidates.size} macro method candidates" } + method_candidates.each do |node| closure = source_map.locate_closure_pin(node.location.line, node.location.column) chain = Solargraph::Parser::ParserGem::NodeChainer.chain(node) if node.children[0].nil? && store.macro_method_name_pins.key?(node.children[1].to_s) diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index d83b2c8e1..968b19af3 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -420,74 +420,104 @@ def host.send_notification method, params puts "Notification: #{method} - #{params}" end - puts 'Parsing and mapping source files...' - prepare_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz", hooks: hooks) do - puts 'Mapping libraries' - host.prepare(directory) - sleep 0.2 until host.libraries.all?(&:mapped?) + parse_path = File.join(options[:output_dir], 'parse_benchmark.json.gz') + catalog_path = File.join(options[:output_dir], 'catalog_benchmark.json.gz') + definition_path = File.join(options[:output_dir], 'definition_benchmark.json.gz') + + prepare_time = catalog_time = definition_time = nil + + # Trap CTRL-C so the in-progress Vernier.profile block can unwind through + # its ensure clause and still write its output file. A second CTRL-C + # restores default handling for a hard exit. + interrupted = false + previous_int_trap = Signal.trap('INT') do + if interrupted + Signal.trap('INT', 'DEFAULT') + Process.kill('INT', Process.pid) + else + interrupted = true + puts "\nInterrupted. Finishing current profile (CTRL-C again to force exit)..." + raise Interrupt + end end - prepare_time = Time.now - prepare_start - puts 'Building the catalog...' - catalog_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz", hooks: hooks) do - host.catalog - end - catalog_time = Time.now - catalog_start + begin + puts 'Parsing and mapping source files...' + prepare_start = Time.now + Vernier.profile(out: parse_path, hooks: hooks) do + puts 'Mapping libraries' + host.prepare(directory) + sleep 0.2 until host.libraries.all?(&:mapped?) + end + prepare_time = Time.now - prepare_start - # Determine test file - if file - test_file = File.join(directory, file) - else - test_file = File.join(directory, 'lib', 'other.rb') - unless File.exist?(test_file) - # Fallback to any Ruby file in the workspace - workspace = Solargraph::Workspace.new(directory) - test_file = workspace.filenames.find { |f| f.end_with?('.rb') } - unless test_file - warn 'No Ruby files found in workspace' - return + puts 'Building the catalog...' + catalog_start = Time.now + Vernier.profile(out: catalog_path, hooks: hooks) do + host.catalog + end + catalog_time = Time.now - catalog_start + + # Determine test file + if file + test_file = File.join(directory, file) + else + test_file = File.join(directory, 'lib', 'other.rb') + unless File.exist?(test_file) + # Fallback to any Ruby file in the workspace + workspace = Solargraph::Workspace.new(directory) + test_file = workspace.filenames.find { |f| f.end_with?('.rb') } + unless test_file + puts 'No Ruby files found in workspace' + return + end end end - end - file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file)) + file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file)) - puts "Profiling go-to-definition for #{test_file}" - puts "Position: line #{options[:line]}, column #{options[:column]}" + puts "Profiling go-to-definition for #{test_file}" + puts "Position: line #{options[:line]}, column #{options[:column]}" - definition_start = Time.now - Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz", hooks: hooks) do - message = Solargraph::LanguageServer::Message::TextDocument::Definition.new( - host, { - 'params' => { - 'textDocument' => { 'uri' => file_uri }, - 'position' => { 'line' => options[:line], 'character' => options[:column] } + definition_start = Time.now + Vernier.profile(out: definition_path, hooks: hooks) do + message = Solargraph::LanguageServer::Message::TextDocument::Definition.new( + host, { + 'params' => { + 'textDocument' => { 'uri' => file_uri }, + 'position' => { 'line' => options[:line], 'character' => options[:column] } + } } - } - ) - puts 'Processing go-to-definition request...' - result = message.process + ) + puts 'Processing go-to-definition request...' + result = message.process + + puts "Result: #{result.inspect}" + end + definition_time = Time.now - definition_start + rescue Interrupt + puts "\nProfile run interrupted; partial profile(s) have been written." + ensure + Signal.trap('INT', previous_int_trap || 'DEFAULT') + + puts "\n=== Timing Results ===" + puts "Parsing & mapping: #{(prepare_time * 1000).round(2)}ms" if prepare_time + puts "Catalog building: #{(catalog_time * 1000).round(2)}ms" if catalog_time + puts "Go-to-definition: #{(definition_time * 1000).round(2)}ms" if definition_time + if prepare_time && catalog_time && definition_time + total_time = prepare_time + catalog_time + definition_time + puts "Total time: #{(total_time * 1000).round(2)}ms" + end + + saved = [parse_path, catalog_path, definition_path].select { |p| File.exist?(p) } + unless saved.empty? + puts "\nProfiles saved to:" + saved.each { |p| puts " - #{File.expand_path(p)}" } - puts "Result: #{result.inspect}" + puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles." + puts 'Or use https://rubygems.org/gems/profile-viewer to view them locally.' + end end - definition_time = Time.now - definition_start - - puts "\n=== Timing Results ===" - puts "Parsing & mapping: #{(prepare_time * 1000).round(2)}ms" - puts "Catalog building: #{(catalog_time * 1000).round(2)}ms" - puts "Go-to-definition: #{(definition_time * 1000).round(2)}ms" - total_time = prepare_time + catalog_time + definition_time - puts "Total time: #{(total_time * 1000).round(2)}ms" - - puts "\nProfiles saved to:" - puts " - #{File.expand_path('parse_benchmark.json.gz', options[:output_dir])}" - puts " - #{File.expand_path('catalog_benchmark.json.gz', options[:output_dir])}" - puts " - #{File.expand_path('definition_benchmark.json.gz', options[:output_dir])}" - - puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles." - puts 'Or use https://rubygems.org/gems/profile-viewer to view them locally.' end private