1+ use anyhow:: anyhow;
12use collections:: { HashMap , HashSet } ;
23use futures:: {
34 channel:: { mpsc, oneshot} ,
45 pin_mut, SinkExt , StreamExt ,
56} ;
67use 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 } ;
89use parking_lot:: Mutex ;
910use project:: { search:: SearchQuery , Fs , Project } ;
1011use regex:: Regex ;
@@ -129,9 +130,29 @@ impl ScriptSession {
129130 "search" ,
130131 lua. create_async_function ( {
131132 let foreground_fns_tx = foreground_fns_tx. clone ( ) ;
132- let fs = fs. clone ( ) ;
133133 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+ }
135156 }
136157 } ) ?,
137158 ) ?;
@@ -211,27 +232,9 @@ impl ScriptSession {
211232 file. set ( "__read_perm" , read_perm) ?;
212233 file. set ( "__write_perm" , write_perm) ?;
213234
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}" ) ) ) ,
235238 } ;
236239
237240 // close method
@@ -567,11 +570,11 @@ impl ScriptSession {
567570 }
568571
569572 async fn search (
570- lua : Lua ,
571- mut foreground_tx : mpsc:: Sender < ForegroundFn > ,
573+ lua : & Lua ,
574+ foreground_tx : & mut mpsc:: Sender < ForegroundFn > ,
572575 fs : Arc < dyn Fs > ,
573576 regex : String ,
574- ) -> mlua :: Result < Table > {
577+ ) -> anyhow :: Result < Table > {
575578 // TODO: Allow specification of these options.
576579 let search_query = SearchQuery :: regex (
577580 & regex,
@@ -584,18 +587,17 @@ impl ScriptSession {
584587 ) ;
585588 let search_query = match search_query {
586589 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) ) ,
588591 } ;
589592
590593 // TODO: Should use `search_query.regex`. The tool description should also be updated,
591594 // as it specifies standard regex.
592595 let search_regex = match Regex :: new ( & regex) {
593596 Ok ( re) => re,
594- Err ( e) => return Err ( mlua :: Error :: runtime ( format ! ( "Invalid regex: {}" , e) ) ) ,
597+ Err ( e) => return Err ( anyhow ! ( "Invalid regex: {}" , e) ) ,
595598 } ;
596599
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 ?;
599601
600602 let mut search_results: Vec < Table > = Vec :: new ( ) ;
601603 while let Some ( path) = abs_paths_rx. next ( ) . await {
@@ -643,7 +645,7 @@ impl ScriptSession {
643645 async fn find_search_candidates (
644646 search_query : SearchQuery ,
645647 foreground_tx : & mut mpsc:: Sender < ForegroundFn > ,
646- ) -> mlua :: Result < mpsc:: UnboundedReceiver < PathBuf > > {
648+ ) -> anyhow :: Result < mpsc:: UnboundedReceiver < PathBuf > > {
647649 Self :: run_foreground_fn (
648650 "finding search file candidates" ,
649651 foreground_tx,
@@ -693,14 +695,62 @@ impl ScriptSession {
693695 } )
694696 } ) ,
695697 )
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 " ) )
697747 }
698748
699749 async fn run_foreground_fn < R : Send + ' static > (
700750 description : & str ,
701751 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 > {
704754 let ( response_tx, response_rx) = oneshot:: channel ( ) ;
705755 let send_result = foreground_tx
706756 . send ( ForegroundFn ( Box :: new ( move |this, cx| {
@@ -710,19 +760,34 @@ impl ScriptSession {
710760 match send_result {
711761 Ok ( ( ) ) => ( ) ,
712762 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+ ) ) ) ;
716766 }
717767 }
718768 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 ! (
724771 "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) )
726791 }
727792 }
728793}
0 commit comments