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

This commit is contained in:
Mikayla Maki 2022-12-08 10:48:28 -08:00
parent 1b8763d0cf
commit 83aefffa38
12 changed files with 270 additions and 300 deletions

4
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}
}

View file

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

View file

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

View file

@ -1,13 +1,18 @@
use crate::persistence::TERMINAL_CONNECTION; mod persistence;
use crate::terminal_view::TerminalView; pub mod terminal_element;
use crate::{Event, TerminalBuilder, TerminalError}; pub mod terminal_view;
use crate::persistence::TERMINAL_DB;
use crate::terminal_view::TerminalView;
use terminal::alacritty_terminal::index::Point;
use terminal::{Event, TerminalBuilder, TerminalError};
use alacritty_terminal::index::Point;
use dirs::home_dir; use dirs::home_dir;
use gpui::{ use gpui::{
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, Task, actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, Task,
View, ViewContext, ViewHandle, WeakViewHandle, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use terminal_view::regex_search_for_query;
use util::{truncate_and_trailoff, ResultExt}; use util::{truncate_and_trailoff, ResultExt};
use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}; use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle};
use workspace::{ use workspace::{
@ -30,6 +35,8 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(TerminalContainer::deploy); cx.add_action(TerminalContainer::deploy);
register_deserializable_item::<TerminalContainer>(cx); 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 //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( pub fn new(
working_directory: Option<PathBuf>, working_directory: Option<PathBuf>,
modal: bool, modal: bool,
workspace_id: WorkspaceId, _workspace_id: WorkspaceId,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
@ -119,8 +126,6 @@ impl TerminalContainer {
settings.terminal_overrides.blinking.clone(), settings.terminal_overrides.blinking.clone(),
scroll, scroll,
cx.window_id(), cx.window_id(),
cx.view_id(),
workspace_id,
) { ) {
Ok(terminal) => { Ok(terminal) => {
let terminal = cx.add_model(|cx| terminal.subscribe(cx)); let terminal = cx.add_model(|cx| terminal.subscribe(cx));
@ -389,7 +394,7 @@ impl Item for TerminalContainer {
item_id: workspace::ItemId, item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>, cx: &mut ViewContext<Pane>,
) -> Task<anyhow::Result<ViewHandle<Self>>> { ) -> 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| { Task::ready(Ok(cx.add_view(|cx| {
TerminalContainer::new( TerminalContainer::new(
working_directory.log_err().flatten(), 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>) { fn added_to_workspace(&mut self, _workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
if let Some(connected) = self.connected() { if let Some(_connected) = self.connected() {
let id = workspace.database_id(); // let id = workspace.database_id();
let terminal_handle = connected.read(cx).terminal().clone(); // let terminal_handle = connected.read(cx).terminal().clone();
terminal_handle.update(cx, |terminal, cx| terminal.set_workspace_id(id, cx)) //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>> { ) -> Task<Vec<Self::Match>> {
if let TerminalContainerContent::Connected(connected) = &self.content { if let TerminalContainerContent::Connected(connected) = &self.content {
let terminal = connected.read(cx).terminal().clone(); 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 { } else {
Task::ready(Vec::new()) Task::ready(Vec::new())
} }
@ -585,21 +597,20 @@ mod tests {
use super::*; use super::*;
use gpui::TestAppContext; use gpui::TestAppContext;
use project::{Entry, Worktree};
use workspace::AppState;
use std::path::Path; use std::path::Path;
use crate::tests::terminal_test_context::TerminalTestContext;
///Working directory calculation tests ///Working directory calculation tests
///No Worktrees in project -> home_dir() ///No Worktrees in project -> home_dir()
#[gpui::test] #[gpui::test]
async fn no_worktree(cx: &mut TestAppContext) { async fn no_worktree(cx: &mut TestAppContext) {
//Setup variables //Setup variables
let mut cx = TerminalTestContext::new(cx); let (project, workspace) = blank_workspace(cx).await;
let (project, workspace) = cx.blank_workspace().await;
//Test //Test
cx.cx.read(|cx| { cx.read(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry(); 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) { async fn no_active_entry_worktree_is_file(cx: &mut TestAppContext) {
//Setup variables //Setup variables
let mut cx = TerminalTestContext::new(cx); let (project, workspace) = blank_workspace(cx).await;
let (project, workspace) = cx.blank_workspace().await; create_file_wt(project.clone(), "/root.txt", cx).await;
cx.create_file_wt(project.clone(), "/root.txt").await;
cx.cx.read(|cx| { cx.read(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry(); let active_entry = project.read(cx).active_entry();
@ -642,12 +652,11 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn no_active_entry_worktree_is_dir(cx: &mut TestAppContext) { async fn no_active_entry_worktree_is_dir(cx: &mut TestAppContext) {
//Setup variables //Setup variables
let mut cx = TerminalTestContext::new(cx); let (project, workspace) = blank_workspace(cx).await;
let (project, workspace) = cx.blank_workspace().await; let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await;
let (_wt, _entry) = cx.create_folder_wt(project.clone(), "/root/").await;
//Test //Test
cx.cx.update(|cx| { cx.update(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry(); let active_entry = project.read(cx).active_entry();
@ -665,14 +674,14 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn active_entry_worktree_is_file(cx: &mut TestAppContext) { async fn active_entry_worktree_is_file(cx: &mut TestAppContext) {
//Setup variables //Setup variables
let mut cx = TerminalTestContext::new(cx);
let (project, workspace) = cx.blank_workspace().await; let (project, workspace) = blank_workspace(cx).await;
let (_wt, _entry) = cx.create_folder_wt(project.clone(), "/root1/").await; let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await;
let (wt2, entry2) = cx.create_file_wt(project.clone(), "/root2.txt").await; let (wt2, entry2) = create_file_wt(project.clone(), "/root2.txt", cx).await;
cx.insert_active_entry_for(wt2, entry2, project.clone()); insert_active_entry_for(wt2, entry2, project.clone(), cx);
//Test //Test
cx.cx.update(|cx| { cx.update(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry(); let active_entry = project.read(cx).active_entry();
@ -689,14 +698,13 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn active_entry_worktree_is_dir(cx: &mut TestAppContext) { async fn active_entry_worktree_is_dir(cx: &mut TestAppContext) {
//Setup variables //Setup variables
let mut cx = TerminalTestContext::new(cx); let (project, workspace) = blank_workspace(cx).await;
let (project, workspace) = cx.blank_workspace().await; let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await;
let (_wt, _entry) = cx.create_folder_wt(project.clone(), "/root1/").await; let (wt2, entry2) = create_folder_wt(project.clone(), "/root2/", cx).await;
let (wt2, entry2) = cx.create_folder_wt(project.clone(), "/root2/").await; insert_active_entry_for(wt2, entry2, project.clone(), cx);
cx.insert_active_entry_for(wt2, entry2, project.clone());
//Test //Test
cx.cx.update(|cx| { cx.update(|cx| {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
let active_entry = project.read(cx).active_entry(); let active_entry = project.read(cx).active_entry();
@ -708,4 +716,84 @@ mod tests {
assert_eq!(res, Some((Path::new("/root1/")).to_path_buf())); 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));
});
}
} }

View file

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

View file

@ -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 context_menu::{ContextMenu, ContextMenuItem};
use gpui::{ use gpui::{
actions, actions,
@ -14,10 +13,17 @@ use gpui::{
use serde::Deserialize; use serde::Deserialize;
use settings::{Settings, TerminalBlink}; use settings::{Settings, TerminalBlink};
use smol::Timer; use smol::Timer;
use terminal::{
alacritty_terminal::{
index::Point,
term::{search::RegexSearch, TermMode},
},
Terminal,
};
use util::ResultExt; use util::ResultExt;
use workspace::pane; 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); const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@ -95,6 +101,22 @@ impl TerminalView {
cx.emit(Event::Wakeup); cx.emit(Event::Wakeup);
} }
Event::BlinkChanged => this.blinking_on = !this.blinking_on, 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), _ => cx.emit(*event),
}) })
.detach(); .detach();
@ -246,8 +268,14 @@ impl TerminalView {
query: project::search::SearchQuery, query: project::search::SearchQuery,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Vec<RangeInclusive<Point>>> { ) -> Task<Vec<RangeInclusive<Point>>> {
self.terminal let searcher = regex_search_for_query(query);
.update(cx, |term, cx| term.find_matches(query, cx))
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> { 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 { impl View for TerminalView {
fn ui_name() -> &'static str { fn ui_name() -> &'static str {
"Terminal" "Terminal"

View file

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