1
+ use anyhow:: anyhow;
1
2
use collections:: { HashMap , HashSet } ;
2
3
use futures:: {
3
4
channel:: { mpsc, oneshot} ,
4
5
pin_mut, SinkExt , StreamExt ,
5
6
} ;
6
7
use gpui:: { AppContext , AsyncApp , Context , Entity , EventEmitter , SharedString , Task , WeakEntity } ;
7
- use mlua:: { Lua , MultiValue , Table , UserData , UserDataMethods } ;
8
+ use mlua:: { ExternalResult , Lua , MultiValue , Table , UserData , UserDataMethods } ;
8
9
use parking_lot:: Mutex ;
9
10
use project:: { search:: SearchQuery , Fs , Project } ;
10
11
use regex:: Regex ;
@@ -129,9 +130,29 @@ impl ScriptSession {
129
130
"search" ,
130
131
lua. create_async_function ( {
131
132
let foreground_fns_tx = foreground_fns_tx. clone ( ) ;
132
- let fs = fs. clone ( ) ;
133
133
move |lua, regex| {
134
- Self :: search ( lua, foreground_fns_tx. clone ( ) , fs. clone ( ) , regex)
134
+ let mut foreground_fns_tx = foreground_fns_tx. clone ( ) ;
135
+ let fs = fs. clone ( ) ;
136
+ async move {
137
+ Self :: search ( & lua, & mut foreground_fns_tx, fs, regex)
138
+ . await
139
+ . into_lua_err ( )
140
+ }
141
+ }
142
+ } ) ?,
143
+ ) ?;
144
+ globals. set (
145
+ "outline" ,
146
+ lua. create_async_function ( {
147
+ let root_dir = root_dir. clone ( ) ;
148
+ move |_lua, path| {
149
+ let mut foreground_fns_tx = foreground_fns_tx. clone ( ) ;
150
+ let root_dir = root_dir. clone ( ) ;
151
+ async move {
152
+ Self :: outline ( root_dir, & mut foreground_fns_tx, path)
153
+ . await
154
+ . into_lua_err ( )
155
+ }
135
156
}
136
157
} ) ?,
137
158
) ?;
@@ -211,27 +232,9 @@ impl ScriptSession {
211
232
file. set ( "__read_perm" , read_perm) ?;
212
233
file. set ( "__write_perm" , write_perm) ?;
213
234
214
- // Sandbox the path; it must be within root_dir
215
- let path: PathBuf = {
216
- let rust_path = Path :: new ( & path_str) ;
217
-
218
- // Get absolute path
219
- if rust_path. is_absolute ( ) {
220
- // Check if path starts with root_dir prefix without resolving symlinks
221
- if !rust_path. starts_with ( & root_dir) {
222
- return Ok ( (
223
- None ,
224
- format ! (
225
- "Error: Absolute path {} is outside the current working directory" ,
226
- path_str
227
- ) ,
228
- ) ) ;
229
- }
230
- rust_path. to_path_buf ( )
231
- } else {
232
- // Make relative path absolute relative to cwd
233
- root_dir. join ( rust_path)
234
- }
235
+ let path = match Self :: parse_abs_path_in_root_dir ( & root_dir, & path_str) {
236
+ Ok ( path) => path,
237
+ Err ( err) => return Ok ( ( None , format ! ( "{err}" ) ) ) ,
235
238
} ;
236
239
237
240
// close method
@@ -567,11 +570,11 @@ impl ScriptSession {
567
570
}
568
571
569
572
async fn search (
570
- lua : Lua ,
571
- mut foreground_tx : mpsc:: Sender < ForegroundFn > ,
573
+ lua : & Lua ,
574
+ foreground_tx : & mut mpsc:: Sender < ForegroundFn > ,
572
575
fs : Arc < dyn Fs > ,
573
576
regex : String ,
574
- ) -> mlua :: Result < Table > {
577
+ ) -> anyhow :: Result < Table > {
575
578
// TODO: Allow specification of these options.
576
579
let search_query = SearchQuery :: regex (
577
580
& regex,
@@ -584,18 +587,17 @@ impl ScriptSession {
584
587
) ;
585
588
let search_query = match search_query {
586
589
Ok ( query) => query,
587
- Err ( e) => return Err ( mlua :: Error :: runtime ( format ! ( "Invalid search query: {}" , e) ) ) ,
590
+ Err ( e) => return Err ( anyhow ! ( "Invalid search query: {}" , e) ) ,
588
591
} ;
589
592
590
593
// TODO: Should use `search_query.regex`. The tool description should also be updated,
591
594
// as it specifies standard regex.
592
595
let search_regex = match Regex :: new ( & regex) {
593
596
Ok ( re) => re,
594
- Err ( e) => return Err ( mlua :: Error :: runtime ( format ! ( "Invalid regex: {}" , e) ) ) ,
597
+ Err ( e) => return Err ( anyhow ! ( "Invalid regex: {}" , e) ) ,
595
598
} ;
596
599
597
- let mut abs_paths_rx =
598
- Self :: find_search_candidates ( search_query, & mut foreground_tx) . await ?;
600
+ let mut abs_paths_rx = Self :: find_search_candidates ( search_query, foreground_tx) . await ?;
599
601
600
602
let mut search_results: Vec < Table > = Vec :: new ( ) ;
601
603
while let Some ( path) = abs_paths_rx. next ( ) . await {
@@ -643,7 +645,7 @@ impl ScriptSession {
643
645
async fn find_search_candidates (
644
646
search_query : SearchQuery ,
645
647
foreground_tx : & mut mpsc:: Sender < ForegroundFn > ,
646
- ) -> mlua :: Result < mpsc:: UnboundedReceiver < PathBuf > > {
648
+ ) -> anyhow :: Result < mpsc:: UnboundedReceiver < PathBuf > > {
647
649
Self :: run_foreground_fn (
648
650
"finding search file candidates" ,
649
651
foreground_tx,
@@ -693,14 +695,62 @@ impl ScriptSession {
693
695
} )
694
696
} ) ,
695
697
)
696
- . await
698
+ . await ?
699
+ }
700
+
701
+ async fn outline (
702
+ root_dir : Option < Arc < Path > > ,
703
+ foreground_tx : & mut mpsc:: Sender < ForegroundFn > ,
704
+ path_str : String ,
705
+ ) -> anyhow:: Result < String > {
706
+ let root_dir = root_dir
707
+ . ok_or_else ( || mlua:: Error :: runtime ( "cannot get outline without a root directory" ) ) ?;
708
+ let path = Self :: parse_abs_path_in_root_dir ( & root_dir, & path_str) ?;
709
+ let outline = Self :: run_foreground_fn (
710
+ "getting code outline" ,
711
+ foreground_tx,
712
+ Box :: new ( move |session, cx| {
713
+ cx. spawn ( move |mut cx| async move {
714
+ // TODO: This will not use file content from `fs_changes`. It will also reflect
715
+ // user changes that have not been saved.
716
+ let buffer = session
717
+ . update ( & mut cx, |session, cx| {
718
+ session
719
+ . project
720
+ . update ( cx, |project, cx| project. open_local_buffer ( & path, cx) )
721
+ } ) ?
722
+ . await ?;
723
+ buffer. update ( & mut cx, |buffer, _cx| {
724
+ if let Some ( outline) = buffer. snapshot ( ) . outline ( None ) {
725
+ Ok ( outline)
726
+ } else {
727
+ Err ( anyhow ! ( "No outline for file {path_str}" ) )
728
+ }
729
+ } )
730
+ } )
731
+ } ) ,
732
+ )
733
+ . await ?
734
+ . await ??;
735
+
736
+ Ok ( outline
737
+ . items
738
+ . into_iter ( )
739
+ . map ( |item| {
740
+ if item. text . contains ( '\n' ) {
741
+ log:: error!( "Outline item unexpectedly contains newline" ) ;
742
+ }
743
+ format ! ( "{}{}" , " " . repeat( item. depth) , item. text)
744
+ } )
745
+ . collect :: < Vec < String > > ( )
746
+ . join ( "\n " ) )
697
747
}
698
748
699
749
async fn run_foreground_fn < R : Send + ' static > (
700
750
description : & str ,
701
751
foreground_tx : & mut mpsc:: Sender < ForegroundFn > ,
702
- function : Box < dyn FnOnce ( WeakEntity < Self > , AsyncApp ) -> anyhow :: Result < R > + Send > ,
703
- ) -> mlua :: Result < R > {
752
+ function : Box < dyn FnOnce ( WeakEntity < Self > , AsyncApp ) -> R + Send > ,
753
+ ) -> anyhow :: Result < R > {
704
754
let ( response_tx, response_rx) = oneshot:: channel ( ) ;
705
755
let send_result = foreground_tx
706
756
. send ( ForegroundFn ( Box :: new ( move |this, cx| {
@@ -710,19 +760,34 @@ impl ScriptSession {
710
760
match send_result {
711
761
Ok ( ( ) ) => ( ) ,
712
762
Err ( err) => {
713
- return Err ( mlua :: Error :: runtime ( format ! (
714
- "Internal error while enqueuing work for {description}: {err} "
715
- ) ) )
763
+ return Err ( anyhow :: Error :: new ( err ) . context ( format ! (
764
+ "Internal error while enqueuing work for {description}"
765
+ ) ) ) ;
716
766
}
717
767
}
718
768
match response_rx. await {
719
- Ok ( Ok ( result) ) => Ok ( result) ,
720
- Ok ( Err ( err) ) => Err ( mlua:: Error :: runtime ( format ! (
721
- "Error while {description}: {err}"
722
- ) ) ) ,
723
- Err ( oneshot:: Canceled ) => Err ( mlua:: Error :: runtime ( format ! (
769
+ Ok ( result) => Ok ( result) ,
770
+ Err ( oneshot:: Canceled ) => Err ( anyhow ! (
724
771
"Internal error: response oneshot was canceled while {description}."
725
- ) ) ) ,
772
+ ) ) ,
773
+ }
774
+ }
775
+
776
+ fn parse_abs_path_in_root_dir ( root_dir : & Path , path_str : & str ) -> anyhow:: Result < PathBuf > {
777
+ let path = Path :: new ( & path_str) ;
778
+ if path. is_absolute ( ) {
779
+ // Check if path starts with root_dir prefix without resolving symlinks
780
+ if path. starts_with ( & root_dir) {
781
+ Ok ( path. to_path_buf ( ) )
782
+ } else {
783
+ Err ( anyhow ! (
784
+ "Error: Absolute path {} is outside the current working directory" ,
785
+ path_str
786
+ ) )
787
+ }
788
+ } else {
789
+ // TODO: Does use of `../` break sandbox - is path canonicalization needed?
790
+ Ok ( root_dir. join ( path) )
726
791
}
727
792
}
728
793
}
0 commit comments