44require "json"
55require "uri"
66
7+ require_relative "code_actions"
78require_relative "implicits"
89
910class SyntaxTree
@@ -22,8 +23,8 @@ def run
2223 end
2324
2425 while headers = input . gets ( "\r \n \r \n " )
25- length = input . read ( headers [ /Content-Length: (\d +)/i , 1 ] . to_i )
26- request = JSON . parse ( length , symbolize_names : true )
26+ source = input . read ( headers [ /Content-Length: (\d +)/i , 1 ] . to_i )
27+ request = JSON . parse ( source , symbolize_names : true )
2728
2829 case request
2930 in { method : "initialize" , id : }
@@ -34,6 +35,8 @@ def run
3435 in { method : "shutdown" }
3536 store . clear
3637 return
38+ in { method : "textDocument/codeAction" , id :, params : { textDocument : { uri : } , range : { start : { line : } } } }
39+ write ( id : id , result : code_actions ( store [ uri ] , line + 1 ) )
3740 in { method : "textDocument/didChange" , params : { textDocument : { uri : } , contentChanges : [ { text : } , *] } }
3841 store [ uri ] = text
3942 in { method : "textDocument/didOpen" , params : { textDocument : { uri :, text : } } }
@@ -42,6 +45,8 @@ def run
4245 store . delete ( uri )
4346 in { method : "textDocument/formatting" , id :, params : { textDocument : { uri : } } }
4447 write ( id : id , result : [ format ( store [ uri ] ) ] )
48+ in { method : "syntaxTree/disasm" , id :, params : { textDocument : { uri :, query : { line :, name : } } } }
49+ write ( id : id , result : disasm ( store [ uri ] , line . to_i , name ) )
4550 in { method : "syntaxTree/implicits" , id :, params : { textDocument : { uri : } } }
4651 write ( id : id , result : implicits ( store [ uri ] ) )
4752 in { method : "syntaxTree/visualizing" , id :, params : { textDocument : { uri : } } }
@@ -60,11 +65,43 @@ def run
6065
6166 def capabilities
6267 {
68+ codeActionProvider : { codeActionsKinds : [ "disasm" ] } ,
6369 documentFormattingProvider : true ,
64- textDocumentSync : { change : 1 , openClose : true } ,
70+ textDocumentSync : { change : 1 , openClose : true }
6571 }
6672 end
6773
74+ def code_actions ( source , line )
75+ actions = CodeActions . find ( SyntaxTree . parse ( source ) , line ) . actions
76+ log ( "Found #{ actions . length } actions on line #{ line } " )
77+
78+ actions . map ( &:as_json )
79+ end
80+
81+ def disasm ( source , line , name )
82+ actions = CodeActions . find ( SyntaxTree . parse ( source ) , line ) . actions
83+ log ( "Disassembling #{ name . inspect } on line #{ line . inspect } " )
84+
85+ matched = actions . detect { |action | action . is_a? ( CodeActions ::DisasmAction ) && action . node . name . value == name }
86+ return "Unable to find method: #{ name } " unless matched
87+
88+ # First, get an instruction sequence that encompasses the method that
89+ # we're going to disassemble. It will include the method declaration,
90+ # which will be the top instruction sequence.
91+ location = matched . node . location
92+ iseq = RubyVM ::InstructionSequence . new ( source [ location . start_char ...location . end_char ] )
93+
94+ # Next, get the first child. We do this because the parent instruction
95+ # sequence is the method declaration, whereas the first child is the body
96+ # of the method, which is what we're interested in.
97+ method = nil
98+ iseq . each_child { |child | method = child }
99+
100+ # Finally, return the disassembly as a string to the server, which will
101+ # serialize it to JSON and return it back to the client.
102+ method . disasm
103+ end
104+
68105 def format ( source )
69106 {
70107 range : {
@@ -75,6 +112,10 @@ def format(source)
75112 }
76113 end
77114
115+ def log ( message )
116+ write ( method : "window/logMessage" , params : { type : 4 , message : message } )
117+ end
118+
78119 def implicits ( source )
79120 implicits = Implicits . find ( SyntaxTree . parse ( source ) )
80121 serialize = -> ( position , text ) { { position : position , text : text } }
0 commit comments