@@ -3,8 +3,15 @@ mod command_executor;
33mod gemset;
44mod language_servers;
55
6+ use std:: { collections:: HashMap , path:: Path } ;
7+
68use language_servers:: { LanguageServer , Rubocop , RubyLsp , Solargraph , Sorbet , Steep } ;
7- use zed_extension_api:: { self as zed} ;
9+ use serde:: { Deserialize , Serialize } ;
10+ use zed_extension_api:: {
11+ self as zed, resolve_tcp_template, Command , DebugAdapterBinary , DebugConfig , DebugRequest ,
12+ DebugScenario , DebugTaskDefinition , StartDebuggingRequestArguments ,
13+ StartDebuggingRequestArgumentsRequest , TcpArgumentsTemplate , Worktree ,
14+ } ;
815
916#[ derive( Default ) ]
1017struct RubyExtension {
@@ -15,6 +22,18 @@ struct RubyExtension {
1522 steep : Option < Steep > ,
1623}
1724
25+ #[ derive( Serialize , Deserialize ) ]
26+ struct RubyDebugConfig {
27+ script_or_command : Option < String > ,
28+ script : Option < String > ,
29+ command : Option < String > ,
30+ #[ serde( default ) ]
31+ args : Vec < String > ,
32+ #[ serde( default ) ]
33+ env : HashMap < String , String > ,
34+ cwd : Option < String > ,
35+ }
36+
1837impl zed:: Extension for RubyExtension {
1938 fn new ( ) -> Self {
2039 Self :: default ( )
@@ -87,6 +106,138 @@ impl zed::Extension for RubyExtension {
87106 _ => None ,
88107 }
89108 }
109+
110+ fn get_dap_binary (
111+ & mut self ,
112+ adapter_name : String ,
113+ config : DebugTaskDefinition ,
114+ _: Option < String > ,
115+ worktree : & Worktree ,
116+ ) -> Result < DebugAdapterBinary , String > {
117+ let mut rdbg_path = Path :: new ( & adapter_name)
118+ . join ( "rdbg" )
119+ . to_string_lossy ( )
120+ . into_owned ( ) ;
121+
122+ if worktree. which ( & rdbg_path) . is_none ( ) {
123+ match worktree. which ( "rdbg" . as_ref ( ) ) {
124+ Some ( path) => rdbg_path = path,
125+ None => {
126+ let output = Command :: new ( "gem" )
127+ . arg ( "install" )
128+ . arg ( "--no-document" )
129+ . arg ( "--bindir" )
130+ . arg ( & adapter_name)
131+ . arg ( "debug" )
132+ . output ( ) ?;
133+ if output. status . is_some_and ( |status| status == 0 ) {
134+ return Err ( format ! (
135+ "Failed to install rdbg:\n {}" ,
136+ String :: from_utf8_lossy( & output. stderr) . into_owned( )
137+ ) ) ;
138+ }
139+ }
140+ }
141+ }
142+
143+ let tcp_connection = config. tcp_connection . unwrap_or ( TcpArgumentsTemplate {
144+ port : None ,
145+ host : None ,
146+ timeout : None ,
147+ } ) ;
148+ let connection = resolve_tcp_template ( tcp_connection) ?;
149+ let mut configuration: serde_json:: Value = serde_json:: from_str ( & config. config )
150+ . map_err ( |e| format ! ( "`config` is not a valid JSON: {e}" ) ) ?;
151+
152+ let ruby_config: RubyDebugConfig = serde_json:: from_value ( configuration. clone ( ) )
153+ . map_err ( |e| format ! ( "`config` is not a valid rdbg config: {e}" ) ) ?;
154+ let mut arguments = vec ! [
155+ "--open" . to_string( ) ,
156+ format!( "--port={}" , connection. port) ,
157+ format!( "--host={}" , connection. host) ,
158+ ] ;
159+ if let Some ( script) = & ruby_config. script {
160+ arguments. push ( script. clone ( ) ) ;
161+ } else if let Some ( command) = & ruby_config. command {
162+ arguments. push ( "--command" . to_string ( ) ) ;
163+ arguments. push ( command. clone ( ) ) ;
164+ } else if let Some ( command_or_script) = & ruby_config. script_or_command {
165+ if worktree. which ( command_or_script) . is_some ( ) {
166+ arguments. push ( "--command" . to_string ( ) ) ;
167+ }
168+ arguments. push ( command_or_script. clone ( ) ) ;
169+ } else {
170+ return Err ( "Ruby debug config must have 'script' or 'command' args" . into ( ) ) ;
171+ }
172+ if let Some ( configuration) = configuration. as_object_mut ( ) {
173+ configuration
174+ . entry ( "cwd" )
175+ . or_insert_with ( || worktree. root_path ( ) . into ( ) ) ;
176+ }
177+ arguments. extend ( ruby_config. args ) ;
178+
179+ Ok ( DebugAdapterBinary {
180+ command : Some ( rdbg_path. to_string ( ) ) ,
181+ arguments,
182+ connection : Some ( connection) ,
183+ cwd : ruby_config. cwd ,
184+ envs : ruby_config. env . into_iter ( ) . collect ( ) ,
185+ request_args : StartDebuggingRequestArguments {
186+ configuration : configuration. to_string ( ) ,
187+ request : StartDebuggingRequestArgumentsRequest :: Launch ,
188+ } ,
189+ } )
190+ }
191+
192+ fn dap_request_kind (
193+ & mut self ,
194+ _: String ,
195+ value : serde_json:: Value ,
196+ ) -> zed_extension_api:: Result < StartDebuggingRequestArgumentsRequest , String > {
197+ value
198+ . get ( "request" )
199+ . and_then ( |request| {
200+ request. as_str ( ) . and_then ( |s| match s {
201+ "launch" => Some ( StartDebuggingRequestArgumentsRequest :: Launch ) ,
202+ "attach" => Some ( StartDebuggingRequestArgumentsRequest :: Attach ) ,
203+ _ => None ,
204+ } )
205+ } )
206+ . ok_or_else ( || {
207+ "Invalid request, expected `request` to be either `launch` or `attach`" . into ( )
208+ } )
209+ }
210+
211+ fn dap_config_to_scenario (
212+ & mut self ,
213+ zed_scenario : DebugConfig ,
214+ ) -> Result < DebugScenario , String > {
215+ match zed_scenario. request {
216+ DebugRequest :: Launch ( launch) => {
217+ let config = RubyDebugConfig {
218+ script_or_command : Some ( launch. program ) ,
219+ script : None ,
220+ command : None ,
221+ args : launch. args ,
222+ env : launch. envs . into_iter ( ) . collect ( ) ,
223+ cwd : launch. cwd . clone ( ) ,
224+ } ;
225+
226+ let config = serde_json:: to_value ( config)
227+ . map_err ( |e| e. to_string ( ) ) ?
228+ . to_string ( ) ;
229+
230+ Ok ( DebugScenario {
231+ adapter : zed_scenario. adapter ,
232+ label : zed_scenario. label ,
233+ config,
234+ tcp_connection : None ,
235+ build : None ,
236+ } )
237+ }
238+ DebugRequest :: Attach ( _) => Err ( "Attach requests are unsupported" . into ( ) ) ,
239+ }
240+ }
90241}
91242
92243zed_extension_api:: register_extension!( RubyExtension ) ;
0 commit comments