Rearrange the terminal code to not have a cyclic dependency with the project

Mikayla Maki created

Change summary

Cargo.lock                                          |   4 
crates/editor/src/editor.rs                         |   2 
crates/project/src/project.rs                       |   8 
crates/terminal/Cargo.toml                          |   6 
crates/terminal/src/terminal.rs                     | 153 +++++--------
crates/terminal/src/tests/terminal_test_context.rs  | 143 -------------
crates/terminal_view/Cargo.toml                     |   2 
crates/terminal_view/src/persistence.rs             |   5 
crates/terminal_view/src/terminal_container_view.rs | 164 +++++++++++---
crates/terminal_view/src/terminal_element.rs        |  31 +-
crates/terminal_view/src/terminal_view.rs           |  46 +++
crates/zed/src/main.rs                              |   4 
12 files changed, 269 insertions(+), 299 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -6271,6 +6271,7 @@ dependencies = [
  "mio-extras",
  "ordered-float",
  "procinfo",
+ "rand 0.8.5",
  "serde",
  "settings",
  "shellexpand",
@@ -6278,13 +6279,13 @@ dependencies = [
  "smol",
  "theme",
  "thiserror",
+ "util",
 ]
 
 [[package]]
 name = "terminal_view"
 version = "0.1.0"
 dependencies = [
- "alacritty_terminal",
  "anyhow",
  "client",
  "context_menu",
@@ -6307,6 +6308,7 @@ dependencies = [
  "shellexpand",
  "smallvec",
  "smol",
+ "terminal",
  "theme",
  "thiserror",
  "util",

crates/editor/src/editor.rs 🔗

@@ -2422,7 +2422,7 @@ impl Editor {
                         let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
                             let excerpt_range = excerpt_range.to_offset(buffer);
                             buffer
-                                .edited_ranges_for_transaction(transaction)
+                                .edited_ranges_for_transaction::<usize>(transaction)
                                 .all(|range| {
                                     excerpt_range.start <= range.start
                                         && excerpt_range.end >= range.end

crates/project/src/project.rs 🔗

@@ -60,9 +60,9 @@ use std::{
         atomic::{AtomicUsize, Ordering::SeqCst},
         Arc,
     },
-    thread::panicking,
     time::Instant,
 };
+use terminal::Terminal;
 use thiserror::Error;
 use util::{defer, post_inc, ResultExt, TryFutureExt as _};
 
@@ -1196,12 +1196,14 @@ impl Project {
 
     pub fn create_terminal_connection(
         &mut self,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<ModelHandle<TerminalConnection>> {
+        _cx: &mut ModelContext<Self>,
+    ) -> Result<ModelHandle<Terminal>> {
         if self.is_remote() {
             return Err(anyhow!(
                 "creating terminals as a guest is not supported yet"
             ));
+        } else {
+            unimplemented!()
         }
     }
 

crates/terminal/Cargo.toml 🔗

@@ -13,6 +13,7 @@ gpui = { path = "../gpui" }
 settings = { path = "../settings" }
 db = { path = "../db" }
 theme = { path = "../theme" }
+util = { path = "../util" }
 alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" }
 procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
 smallvec = { version = "1.6", features = ["union"] }
@@ -27,4 +28,7 @@ libc = "0.2"
 anyhow = "1"
 thiserror = "1.0"
 lazy_static = "1.4.0"
-serde = { version = "1.0", features = ["derive"] }
+serde = { version = "1.0", features = ["derive"] }
+
+[dev-dependencies]
+rand = "0.8.5"

crates/terminal/src/terminal.rs 🔗

@@ -1,5 +1,5 @@
 pub mod mappings;
-mod persistence;
+pub use alacritty_terminal;
 
 use alacritty_terminal::{
     ansi::{ClearMode, Handler},
@@ -30,7 +30,6 @@ use mappings::mouse::{
     alt_scroll, grid_point, mouse_button_report, mouse_moved_report, mouse_side, scroll_report,
 };
 
-use persistence::TERMINAL_CONNECTION;
 use procinfo::LocalProcessInfo;
 use settings::{AlternateScroll, Settings, Shell, TerminalBlink};
 use util::ResultExt;
@@ -53,8 +52,7 @@ use gpui::{
     geometry::vector::{vec2f, Vector2F},
     keymap::Keystroke,
     scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp},
-    AppContext, ClipboardItem, Entity, ModelContext, MouseButton, MouseMovedEvent,
-    MutableAppContext, Task,
+    ClipboardItem, Entity, ModelContext, MouseButton, MouseMovedEvent, Task,
 };
 
 use crate::mappings::{
@@ -63,12 +61,6 @@ use crate::mappings::{
 };
 use lazy_static::lazy_static;
 
-///Initialize and register all of our action handlers
-pub fn init(cx: &mut MutableAppContext) {
-    terminal_view::init(cx);
-    terminal_container_view::init(cx);
-}
-
 ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
 ///Scroll multiplier that is set to 3 by default. This will be removed when I
 ///Implement scroll bars.
@@ -124,10 +116,10 @@ impl EventListener for ZedListener {
 
 #[derive(Clone, Copy, Debug)]
 pub struct TerminalSize {
-    cell_width: f32,
-    line_height: f32,
-    height: f32,
-    width: f32,
+    pub cell_width: f32,
+    pub line_height: f32,
+    pub height: f32,
+    pub width: f32,
 }
 
 impl TerminalSize {
@@ -281,8 +273,6 @@ impl TerminalBuilder {
         blink_settings: Option<TerminalBlink>,
         alternate_scroll: &AlternateScroll,
         window_id: usize,
-        item_id: ItemId,
-        workspace_id: WorkspaceId,
     ) -> Result<TerminalBuilder> {
         let pty_config = {
             let alac_shell = shell.clone().and_then(|shell| match shell {
@@ -387,8 +377,6 @@ impl TerminalBuilder {
             last_mouse_position: None,
             next_link_id: 0,
             selection_phase: SelectionPhase::Ended,
-            workspace_id,
-            item_id,
         };
 
         Ok(TerminalBuilder {
@@ -460,9 +448,9 @@ impl TerminalBuilder {
 }
 
 #[derive(Debug, Clone)]
-struct IndexedCell {
-    point: Point,
-    cell: Cell,
+pub struct IndexedCell {
+    pub point: Point,
+    pub cell: Cell,
 }
 
 impl Deref for IndexedCell {
@@ -474,17 +462,18 @@ impl Deref for IndexedCell {
     }
 }
 
+// TODO: Un-pub
 #[derive(Clone)]
 pub struct TerminalContent {
-    cells: Vec<IndexedCell>,
-    mode: TermMode,
-    display_offset: usize,
-    selection_text: Option<String>,
-    selection: Option<SelectionRange>,
-    cursor: RenderableCursor,
-    cursor_char: char,
-    size: TerminalSize,
-    last_hovered_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
+    pub cells: Vec<IndexedCell>,
+    pub mode: TermMode,
+    pub display_offset: usize,
+    pub selection_text: Option<String>,
+    pub selection: Option<SelectionRange>,
+    pub cursor: RenderableCursor,
+    pub cursor_char: char,
+    pub size: TerminalSize,
+    pub last_hovered_hyperlink: Option<(String, RangeInclusive<Point>, usize)>,
 }
 
 impl Default for TerminalContent {
@@ -521,19 +510,17 @@ pub struct Terminal {
     /// This is only used for terminal hyperlink checking
     last_mouse_position: Option<Vector2F>,
     pub matches: Vec<RangeInclusive<Point>>,
-    last_content: TerminalContent,
+    pub last_content: TerminalContent,
     last_synced: Instant,
     sync_task: Option<Task<()>>,
-    selection_head: Option<Point>,
-    breadcrumb_text: String,
+    pub selection_head: Option<Point>,
+    pub breadcrumb_text: String,
     shell_pid: u32,
     shell_fd: u32,
-    foreground_process_info: Option<LocalProcessInfo>,
+    pub foreground_process_info: Option<LocalProcessInfo>,
     scroll_px: f32,
     next_link_id: usize,
     selection_phase: SelectionPhase,
-    workspace_id: WorkspaceId,
-    item_id: ItemId,
 }
 
 impl Terminal {
@@ -574,20 +561,6 @@ impl Terminal {
 
                 if self.update_process_info() {
                     cx.emit(Event::TitleChanged);
-
-                    if let Some(foreground_info) = &self.foreground_process_info {
-                        let cwd = foreground_info.cwd.clone();
-                        let item_id = self.item_id;
-                        let workspace_id = self.workspace_id;
-                        cx.background()
-                            .spawn(async move {
-                                TERMINAL_CONNECTION
-                                    .save_working_directory(item_id, workspace_id, cwd)
-                                    .await
-                                    .log_err();
-                            })
-                            .detach();
-                    }
                 }
             }
             AlacTermEvent::ColorRequest(idx, fun_ptr) => {
@@ -1190,42 +1163,13 @@ impl Terminal {
         }
     }
 
-    pub fn set_workspace_id(&mut self, id: WorkspaceId, cx: &AppContext) {
-        let old_workspace_id = self.workspace_id;
-        let item_id = self.item_id;
-        cx.background()
-            .spawn(async move {
-                TERMINAL_CONNECTION
-                    .update_workspace_id(id, old_workspace_id, item_id)
-                    .await
-                    .log_err()
-            })
-            .detach();
-
-        self.workspace_id = id;
-    }
-
     pub fn find_matches(
         &mut self,
-        query: project::search::SearchQuery,
+        searcher: RegexSearch,
         cx: &mut ModelContext<Self>,
     ) -> Task<Vec<RangeInclusive<Point>>> {
         let term = self.term.clone();
         cx.background().spawn(async move {
-            let searcher = match query {
-                project::search::SearchQuery::Text { query, .. } => {
-                    RegexSearch::new(query.as_ref())
-                }
-                project::search::SearchQuery::Regex { query, .. } => {
-                    RegexSearch::new(query.as_ref())
-                }
-            };
-
-            if searcher.is_err() {
-                return Vec::new();
-            }
-            let searcher = searcher.unwrap();
-
             let term = term.lock();
 
             all_search_matches(&term, &searcher).collect()
@@ -1322,14 +1266,14 @@ fn open_uri(uri: &str) -> Result<(), std::io::Error> {
 
 #[cfg(test)]
 mod tests {
+    use alacritty_terminal::{
+        index::{Column, Line, Point},
+        term::cell::Cell,
+    };
     use gpui::geometry::vector::vec2f;
-    use rand::{thread_rng, Rng};
-
-    use crate::content_index_for_mouse;
+    use rand::{rngs::ThreadRng, thread_rng, Rng};
 
-    use self::terminal_test_context::TerminalTestContext;
-
-    pub mod terminal_test_context;
+    use crate::{content_index_for_mouse, IndexedCell, TerminalContent, TerminalSize};
 
     #[test]
     fn test_mouse_to_cell() {
@@ -1346,7 +1290,7 @@ mod tests {
                 width: cell_size * (viewport_cells as f32),
             };
 
-            let (content, cells) = TerminalTestContext::create_terminal_content(size, &mut rng);
+            let (content, cells) = create_terminal_content(size, &mut rng);
 
             for i in 0..(viewport_cells - 1) {
                 let i = i as usize;
@@ -1382,7 +1326,7 @@ mod tests {
             width: 100.,
         };
 
-        let (content, cells) = TerminalTestContext::create_terminal_content(size, &mut rng);
+        let (content, cells) = create_terminal_content(size, &mut rng);
 
         assert_eq!(
             content.cells[content_index_for_mouse(vec2f(-10., -10.), &content)].c,
@@ -1393,4 +1337,37 @@ mod tests {
             cells[9][9]
         );
     }
+
+    fn create_terminal_content(
+        size: TerminalSize,
+        rng: &mut ThreadRng,
+    ) -> (TerminalContent, Vec<Vec<char>>) {
+        let mut ic = Vec::new();
+        let mut cells = Vec::new();
+
+        for row in 0..((size.height() / size.line_height()) as usize) {
+            let mut row_vec = Vec::new();
+            for col in 0..((size.width() / size.cell_width()) as usize) {
+                let cell_char = rng.gen();
+                ic.push(IndexedCell {
+                    point: Point::new(Line(row as i32), Column(col)),
+                    cell: Cell {
+                        c: cell_char,
+                        ..Default::default()
+                    },
+                });
+                row_vec.push(cell_char)
+            }
+            cells.push(row_vec)
+        }
+
+        (
+            TerminalContent {
+                cells: ic,
+                size,
+                ..Default::default()
+            },
+            cells,
+        )
+    }
 }

crates/terminal/src/tests/terminal_test_context.rs 🔗

@@ -1,143 +0,0 @@
-use std::{path::Path, time::Duration};
-
-use alacritty_terminal::{
-    index::{Column, Line, Point},
-    term::cell::Cell,
-};
-use gpui::{ModelHandle, TestAppContext, ViewHandle};
-
-use project::{Entry, Project, ProjectPath, Worktree};
-use rand::{rngs::ThreadRng, Rng};
-use workspace::{AppState, Workspace};
-
-use crate::{IndexedCell, TerminalContent, TerminalSize};
-
-pub struct TerminalTestContext<'a> {
-    pub cx: &'a mut TestAppContext,
-}
-
-impl<'a> TerminalTestContext<'a> {
-    pub fn new(cx: &'a mut TestAppContext) -> Self {
-        cx.set_condition_duration(Some(Duration::from_secs(5)));
-
-        TerminalTestContext { cx }
-    }
-
-    ///Creates a worktree with 1 file: /root.txt
-    pub async fn blank_workspace(&mut self) -> (ModelHandle<Project>, ViewHandle<Workspace>) {
-        let params = self.cx.update(AppState::test);
-
-        let project = Project::test(params.fs.clone(), [], self.cx).await;
-        let (_, workspace) = self.cx.add_window(|cx| {
-            Workspace::new(
-                Default::default(),
-                0,
-                project.clone(),
-                |_, _| unimplemented!(),
-                cx,
-            )
-        });
-
-        (project, workspace)
-    }
-
-    ///Creates a worktree with 1 folder: /root{suffix}/
-    pub async fn create_folder_wt(
-        &mut self,
-        project: ModelHandle<Project>,
-        path: impl AsRef<Path>,
-    ) -> (ModelHandle<Worktree>, Entry) {
-        self.create_wt(project, true, path).await
-    }
-
-    ///Creates a worktree with 1 file: /root{suffix}.txt
-    pub async fn create_file_wt(
-        &mut self,
-        project: ModelHandle<Project>,
-        path: impl AsRef<Path>,
-    ) -> (ModelHandle<Worktree>, Entry) {
-        self.create_wt(project, false, path).await
-    }
-
-    async fn create_wt(
-        &mut self,
-        project: ModelHandle<Project>,
-        is_dir: bool,
-        path: impl AsRef<Path>,
-    ) -> (ModelHandle<Worktree>, Entry) {
-        let (wt, _) = project
-            .update(self.cx, |project, cx| {
-                project.find_or_create_local_worktree(path, true, cx)
-            })
-            .await
-            .unwrap();
-
-        let entry = self
-            .cx
-            .update(|cx| {
-                wt.update(cx, |wt, cx| {
-                    wt.as_local()
-                        .unwrap()
-                        .create_entry(Path::new(""), is_dir, cx)
-                })
-            })
-            .await
-            .unwrap();
-
-        (wt, entry)
-    }
-
-    pub fn insert_active_entry_for(
-        &mut self,
-        wt: ModelHandle<Worktree>,
-        entry: Entry,
-        project: ModelHandle<Project>,
-    ) {
-        self.cx.update(|cx| {
-            let p = ProjectPath {
-                worktree_id: wt.read(cx).id(),
-                path: entry.path,
-            };
-            project.update(cx, |project, cx| project.set_active_path(Some(p), cx));
-        });
-    }
-
-    pub fn create_terminal_content(
-        size: TerminalSize,
-        rng: &mut ThreadRng,
-    ) -> (TerminalContent, Vec<Vec<char>>) {
-        let mut ic = Vec::new();
-        let mut cells = Vec::new();
-
-        for row in 0..((size.height() / size.line_height()) as usize) {
-            let mut row_vec = Vec::new();
-            for col in 0..((size.width() / size.cell_width()) as usize) {
-                let cell_char = rng.gen();
-                ic.push(IndexedCell {
-                    point: Point::new(Line(row as i32), Column(col)),
-                    cell: Cell {
-                        c: cell_char,
-                        ..Default::default()
-                    },
-                });
-                row_vec.push(cell_char)
-            }
-            cells.push(row_vec)
-        }
-
-        (
-            TerminalContent {
-                cells: ic,
-                size,
-                ..Default::default()
-            },
-            cells,
-        )
-    }
-}
-
-impl<'a> Drop for TerminalTestContext<'a> {
-    fn drop(&mut self) {
-        self.cx.set_condition_duration(None);
-    }
-}

crates/terminal_view/Cargo.toml 🔗

@@ -18,8 +18,8 @@ theme = { path = "../theme" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
 db = { path = "../db" }
-alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" }
 procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
+terminal = { path = "../terminal" }
 smallvec = { version = "1.6", features = ["union"] }
 smol = "1.2.5"
 mio-extras = "2.0.6"

crates/terminal/src/persistence.rs → crates/terminal_view/src/persistence.rs 🔗

@@ -1,11 +1,12 @@
 use std::path::PathBuf;
 
 use db::{define_connection, query, sqlez_macros::sql};
+use workspace::{WorkspaceDb, WorkspaceId};
 
 type ModelId = usize;
 
 define_connection! {
-    pub static ref TERMINAL_CONNECTION: TerminalDb<()> =
+    pub static ref TERMINAL_DB: TerminalDb<WorkspaceDb> =
         &[sql!(
             CREATE TABLE terminals (
                 workspace_id INTEGER,
@@ -34,7 +35,7 @@ impl TerminalDb {
     query! {
         pub async fn save_working_directory(
             item_id: ModelId,
-            workspace_id: WorkspaceId,
+            workspace_id: i64,
             working_directory: PathBuf
         ) -> Result<()> {
             INSERT OR REPLACE INTO terminals(item_id, workspace_id, working_directory)

crates/terminal_view/src/terminal_container_view.rs 🔗

@@ -1,13 +1,18 @@
-use crate::persistence::TERMINAL_CONNECTION;
+mod persistence;
+pub mod terminal_element;
+pub mod terminal_view;
+
+use crate::persistence::TERMINAL_DB;
 use crate::terminal_view::TerminalView;
-use crate::{Event, TerminalBuilder, TerminalError};
+use terminal::alacritty_terminal::index::Point;
+use terminal::{Event, TerminalBuilder, TerminalError};
 
-use alacritty_terminal::index::Point;
 use dirs::home_dir;
 use gpui::{
     actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, Task,
     View, ViewContext, ViewHandle, WeakViewHandle,
 };
+use terminal_view::regex_search_for_query;
 use util::{truncate_and_trailoff, ResultExt};
 use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle};
 use workspace::{
@@ -30,6 +35,8 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(TerminalContainer::deploy);
 
     register_deserializable_item::<TerminalContainer>(cx);
+
+    terminal_view::init(cx);
 }
 
 //Make terminal view an enum, that can give you views for the error and non-error states
@@ -92,7 +99,7 @@ impl TerminalContainer {
     pub fn new(
         working_directory: Option<PathBuf>,
         modal: bool,
-        workspace_id: WorkspaceId,
+        _workspace_id: WorkspaceId,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let settings = cx.global::<Settings>();
@@ -119,8 +126,6 @@ impl TerminalContainer {
             settings.terminal_overrides.blinking.clone(),
             scroll,
             cx.window_id(),
-            cx.view_id(),
-            workspace_id,
         ) {
             Ok(terminal) => {
                 let terminal = cx.add_model(|cx| terminal.subscribe(cx));
@@ -389,7 +394,7 @@ impl Item for TerminalContainer {
         item_id: workspace::ItemId,
         cx: &mut ViewContext<Pane>,
     ) -> Task<anyhow::Result<ViewHandle<Self>>> {
-        let working_directory = TERMINAL_CONNECTION.get_working_directory(item_id, workspace_id);
+        let working_directory = TERMINAL_DB.get_working_directory(item_id, workspace_id);
         Task::ready(Ok(cx.add_view(|cx| {
             TerminalContainer::new(
                 working_directory.log_err().flatten(),
@@ -400,11 +405,14 @@ impl Item for TerminalContainer {
         })))
     }
 
-    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
-        if let Some(connected) = self.connected() {
-            let id = workspace.database_id();
-            let terminal_handle = connected.read(cx).terminal().clone();
-            terminal_handle.update(cx, |terminal, cx| terminal.set_workspace_id(id, cx))
+    fn added_to_workspace(&mut self, _workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
+        if let Some(_connected) = self.connected() {
+            // let id = workspace.database_id();
+            // let terminal_handle = connected.read(cx).terminal().clone();
+            //TODO
+            cx.background()
+                .spawn(TERMINAL_DB.update_workspace_id(0, 0, 0))
+                .detach();
         }
     }
 }
@@ -477,7 +485,11 @@ impl SearchableItem for TerminalContainer {
     ) -> Task<Vec<Self::Match>> {
         if let TerminalContainerContent::Connected(connected) = &self.content {
             let terminal = connected.read(cx).terminal().clone();
-            terminal.update(cx, |term, cx| term.find_matches(query, cx))
+            if let Some(searcher) = regex_search_for_query(query) {
+                terminal.update(cx, |term, cx| term.find_matches(searcher, cx))
+            } else {
+                cx.background().spawn(async { Vec::new() })
+            }
         } else {
             Task::ready(Vec::new())
         }
@@ -585,21 +597,20 @@ mod tests {
 
     use super::*;
     use gpui::TestAppContext;
+    use project::{Entry, Worktree};
+    use workspace::AppState;
 
     use std::path::Path;
 
-    use crate::tests::terminal_test_context::TerminalTestContext;
-
     ///Working directory calculation tests
 
     ///No Worktrees in project -> home_dir()
     #[gpui::test]
     async fn no_worktree(cx: &mut TestAppContext) {
         //Setup variables
-        let mut cx = TerminalTestContext::new(cx);
-        let (project, workspace) = cx.blank_workspace().await;
+        let (project, workspace) = blank_workspace(cx).await;
         //Test
-        cx.cx.read(|cx| {
+        cx.read(|cx| {
             let workspace = workspace.read(cx);
             let active_entry = project.read(cx).active_entry();
 
@@ -619,11 +630,10 @@ mod tests {
     async fn no_active_entry_worktree_is_file(cx: &mut TestAppContext) {
         //Setup variables
 
-        let mut cx = TerminalTestContext::new(cx);
-        let (project, workspace) = cx.blank_workspace().await;
-        cx.create_file_wt(project.clone(), "/root.txt").await;
+        let (project, workspace) = blank_workspace(cx).await;
+        create_file_wt(project.clone(), "/root.txt", cx).await;
 
-        cx.cx.read(|cx| {
+        cx.read(|cx| {
             let workspace = workspace.read(cx);
             let active_entry = project.read(cx).active_entry();
 
@@ -642,12 +652,11 @@ mod tests {
     #[gpui::test]
     async fn no_active_entry_worktree_is_dir(cx: &mut TestAppContext) {
         //Setup variables
-        let mut cx = TerminalTestContext::new(cx);
-        let (project, workspace) = cx.blank_workspace().await;
-        let (_wt, _entry) = cx.create_folder_wt(project.clone(), "/root/").await;
+        let (project, workspace) = blank_workspace(cx).await;
+        let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await;
 
         //Test
-        cx.cx.update(|cx| {
+        cx.update(|cx| {
             let workspace = workspace.read(cx);
             let active_entry = project.read(cx).active_entry();
 
@@ -665,14 +674,14 @@ mod tests {
     #[gpui::test]
     async fn active_entry_worktree_is_file(cx: &mut TestAppContext) {
         //Setup variables
-        let mut cx = TerminalTestContext::new(cx);
-        let (project, workspace) = cx.blank_workspace().await;
-        let (_wt, _entry) = cx.create_folder_wt(project.clone(), "/root1/").await;
-        let (wt2, entry2) = cx.create_file_wt(project.clone(), "/root2.txt").await;
-        cx.insert_active_entry_for(wt2, entry2, project.clone());
+
+        let (project, workspace) = blank_workspace(cx).await;
+        let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await;
+        let (wt2, entry2) = create_file_wt(project.clone(), "/root2.txt", cx).await;
+        insert_active_entry_for(wt2, entry2, project.clone(), cx);
 
         //Test
-        cx.cx.update(|cx| {
+        cx.update(|cx| {
             let workspace = workspace.read(cx);
             let active_entry = project.read(cx).active_entry();
 
@@ -689,14 +698,13 @@ mod tests {
     #[gpui::test]
     async fn active_entry_worktree_is_dir(cx: &mut TestAppContext) {
         //Setup variables
-        let mut cx = TerminalTestContext::new(cx);
-        let (project, workspace) = cx.blank_workspace().await;
-        let (_wt, _entry) = cx.create_folder_wt(project.clone(), "/root1/").await;
-        let (wt2, entry2) = cx.create_folder_wt(project.clone(), "/root2/").await;
-        cx.insert_active_entry_for(wt2, entry2, project.clone());
+        let (project, workspace) = blank_workspace(cx).await;
+        let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await;
+        let (wt2, entry2) = create_folder_wt(project.clone(), "/root2/", cx).await;
+        insert_active_entry_for(wt2, entry2, project.clone(), cx);
 
         //Test
-        cx.cx.update(|cx| {
+        cx.update(|cx| {
             let workspace = workspace.read(cx);
             let active_entry = project.read(cx).active_entry();
 
@@ -708,4 +716,84 @@ mod tests {
             assert_eq!(res, Some((Path::new("/root1/")).to_path_buf()));
         });
     }
+
+    ///Creates a worktree with 1 file: /root.txt
+    pub async fn blank_workspace(
+        cx: &mut TestAppContext,
+    ) -> (ModelHandle<Project>, ViewHandle<Workspace>) {
+        let params = cx.update(AppState::test);
+
+        let project = Project::test(params.fs.clone(), [], cx).await;
+        let (_, workspace) = cx.add_window(|cx| {
+            Workspace::new(
+                Default::default(),
+                0,
+                project.clone(),
+                |_, _| unimplemented!(),
+                cx,
+            )
+        });
+
+        (project, workspace)
+    }
+
+    ///Creates a worktree with 1 folder: /root{suffix}/
+    async fn create_folder_wt(
+        project: ModelHandle<Project>,
+        path: impl AsRef<Path>,
+        cx: &mut TestAppContext,
+    ) -> (ModelHandle<Worktree>, Entry) {
+        create_wt(project, true, path, cx).await
+    }
+
+    ///Creates a worktree with 1 file: /root{suffix}.txt
+    async fn create_file_wt(
+        project: ModelHandle<Project>,
+        path: impl AsRef<Path>,
+        cx: &mut TestAppContext,
+    ) -> (ModelHandle<Worktree>, Entry) {
+        create_wt(project, false, path, cx).await
+    }
+
+    async fn create_wt(
+        project: ModelHandle<Project>,
+        is_dir: bool,
+        path: impl AsRef<Path>,
+        cx: &mut TestAppContext,
+    ) -> (ModelHandle<Worktree>, Entry) {
+        let (wt, _) = project
+            .update(cx, |project, cx| {
+                project.find_or_create_local_worktree(path, true, cx)
+            })
+            .await
+            .unwrap();
+
+        let entry = cx
+            .update(|cx| {
+                wt.update(cx, |wt, cx| {
+                    wt.as_local()
+                        .unwrap()
+                        .create_entry(Path::new(""), is_dir, cx)
+                })
+            })
+            .await
+            .unwrap();
+
+        (wt, entry)
+    }
+
+    pub fn insert_active_entry_for(
+        wt: ModelHandle<Worktree>,
+        entry: Entry,
+        project: ModelHandle<Project>,
+        cx: &mut TestAppContext,
+    ) {
+        cx.update(|cx| {
+            let p = ProjectPath {
+                worktree_id: wt.read(cx).id(),
+                path: entry.path,
+            };
+            project.update(cx, |project, cx| project.set_active_path(Some(p), cx));
+        });
+    }
 }

crates/terminal_view/src/terminal_element.rs 🔗

@@ -1,9 +1,3 @@
-use alacritty_terminal::{
-    ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
-    grid::Dimensions,
-    index::Point,
-    term::{cell::Flags, TermMode},
-};
 use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
 use gpui::{
     color::Color,
@@ -22,17 +16,23 @@ use itertools::Itertools;
 use language::CursorShape;
 use ordered_float::OrderedFloat;
 use settings::Settings;
+use terminal::{
+    alacritty_terminal::{
+        ansi::{Color as AnsiColor, CursorShape as AlacCursorShape, NamedColor},
+        grid::Dimensions,
+        index::Point,
+        term::{cell::Flags, TermMode},
+    },
+    mappings::colors::convert_color,
+    IndexedCell, Terminal, TerminalContent, TerminalSize,
+};
 use theme::TerminalStyle;
 use util::ResultExt;
 
 use std::{fmt::Debug, ops::RangeInclusive};
 use std::{mem, ops::Range};
 
-use crate::{
-    mappings::colors::convert_color,
-    terminal_view::{DeployContextMenu, TerminalView},
-    IndexedCell, Terminal, TerminalContent, TerminalSize,
-};
+use crate::terminal_view::{DeployContextMenu, TerminalView};
 
 ///The information generated during layout that is nescessary for painting
 pub struct LayoutState {
@@ -198,7 +198,10 @@ impl TerminalElement {
 
                 //Expand background rect range
                 {
-                    if matches!(bg, Named(NamedColor::Background)) {
+                    if matches!(
+                        bg,
+                        terminal::alacritty_terminal::ansi::Color::Named(NamedColor::Background)
+                    ) {
                         //Continue to next cell, resetting variables if nescessary
                         cur_alac_color = None;
                         if let Some(rect) = cur_rect {
@@ -299,7 +302,7 @@ impl TerminalElement {
     ///Convert the Alacritty cell styles to GPUI text styles and background color
     fn cell_style(
         indexed: &IndexedCell,
-        fg: AnsiColor,
+        fg: terminal::alacritty_terminal::ansi::Color,
         style: &TerminalStyle,
         text_style: &TextStyle,
         font_cache: &FontCache,
@@ -636,7 +639,7 @@ impl Element for TerminalElement {
 
         //Layout cursor. Rectangle is used for IME, so we should lay it out even
         //if we don't end up showing it.
-        let cursor = if let AlacCursorShape::Hidden = cursor.shape {
+        let cursor = if let terminal::alacritty_terminal::ansi::CursorShape::Hidden = cursor.shape {
             None
         } else {
             let cursor_point = DisplayCursor::from(cursor.point, *display_offset);

crates/terminal_view/src/terminal_view.rs 🔗

@@ -1,6 +1,5 @@
-use std::{ops::RangeInclusive, time::Duration};
+use std::{ops::RangeInclusive, path::PathBuf, time::Duration};
 
-use alacritty_terminal::{index::Point, term::TermMode};
 use context_menu::{ContextMenu, ContextMenuItem};
 use gpui::{
     actions,
@@ -14,10 +13,17 @@ use gpui::{
 use serde::Deserialize;
 use settings::{Settings, TerminalBlink};
 use smol::Timer;
+use terminal::{
+    alacritty_terminal::{
+        index::Point,
+        term::{search::RegexSearch, TermMode},
+    },
+    Terminal,
+};
 use util::ResultExt;
 use workspace::pane;
 
-use crate::{terminal_element::TerminalElement, Event, Terminal};
+use crate::{persistence::TERMINAL_DB, terminal_element::TerminalElement, Event};
 
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
 
@@ -95,6 +101,22 @@ impl TerminalView {
                 cx.emit(Event::Wakeup);
             }
             Event::BlinkChanged => this.blinking_on = !this.blinking_on,
+            Event::TitleChanged => {
+                // if let Some(foreground_info) = &terminal.read(cx).foreground_process_info {
+                // let cwd = foreground_info.cwd.clone();
+                //TODO
+                // let item_id = self.item_id;
+                // let workspace_id = self.workspace_id;
+                cx.background()
+                    .spawn(async move {
+                        TERMINAL_DB
+                            .save_working_directory(0, 0, PathBuf::new())
+                            .await
+                            .log_err();
+                    })
+                    .detach();
+                // }
+            }
             _ => cx.emit(*event),
         })
         .detach();
@@ -246,8 +268,14 @@ impl TerminalView {
         query: project::search::SearchQuery,
         cx: &mut ViewContext<Self>,
     ) -> Task<Vec<RangeInclusive<Point>>> {
-        self.terminal
-            .update(cx, |term, cx| term.find_matches(query, cx))
+        let searcher = regex_search_for_query(query);
+
+        if let Some(searcher) = searcher {
+            self.terminal
+                .update(cx, |term, cx| term.find_matches(searcher, cx))
+        } else {
+            cx.background().spawn(async { Vec::new() })
+        }
     }
 
     pub fn terminal(&self) -> &ModelHandle<Terminal> {
@@ -302,6 +330,14 @@ impl TerminalView {
     }
 }
 
+pub fn regex_search_for_query(query: project::search::SearchQuery) -> Option<RegexSearch> {
+    let searcher = match query {
+        project::search::SearchQuery::Text { query, .. } => RegexSearch::new(&query),
+        project::search::SearchQuery::Regex { query, .. } => RegexSearch::new(&query),
+    };
+    searcher.ok()
+}
+
 impl View for TerminalView {
     fn ui_name() -> &'static str {
         "Terminal"

crates/zed/src/main.rs 🔗

@@ -32,7 +32,7 @@ use settings::{
 use smol::process::Command;
 use std::fs::OpenOptions;
 use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration};
-use terminal::terminal_container_view::{get_working_directory, TerminalContainer};
+use terminal_view::{get_working_directory, TerminalContainer};
 
 use fs::RealFs;
 use settings::watched_json::{watch_keymap_file, watch_settings_file, WatchedJsonFile};
@@ -119,7 +119,7 @@ fn main() {
         diagnostics::init(cx);
         search::init(cx);
         vim::init(cx);
-        terminal::init(cx);
+        terminal_view::init(cx);
         theme_testbench::init(cx);
 
         cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))