1use crate::Project;
2use gpui::{AnyWindowHandle, ModelContext, ModelHandle, WeakModelHandle};
3use std::path::PathBuf;
4use terminal::{Terminal, TerminalBuilder, TerminalSettings};
5
6#[cfg(target_os = "macos")]
7use std::os::unix::ffi::OsStrExt;
8
9pub struct Terminals {
10 pub(crate) local_handles: Vec<WeakModelHandle<terminal::Terminal>>,
11}
12
13impl Project {
14 pub fn create_terminal(
15 &mut self,
16 working_directory: Option<PathBuf>,
17 window: AnyWindowHandle,
18 cx: &mut ModelContext<Self>,
19 ) -> anyhow::Result<ModelHandle<Terminal>> {
20 if self.is_remote() {
21 return Err(anyhow::anyhow!(
22 "creating terminals as a guest is not supported yet"
23 ));
24 } else {
25 let settings = settings::get::<TerminalSettings>(cx);
26
27 let terminal = TerminalBuilder::new(
28 working_directory.clone(),
29 settings.shell.clone(),
30 settings.env.clone(),
31 Some(settings.blinking.clone()),
32 settings.alternate_scroll,
33 window,
34 )
35 .map(|builder| {
36 let terminal_handle = cx.add_model(|cx| builder.subscribe(cx));
37
38 self.terminals
39 .local_handles
40 .push(terminal_handle.downgrade());
41
42 let id = terminal_handle.id();
43 cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
44 let handles = &mut project.terminals.local_handles;
45
46 if let Some(index) = handles.iter().position(|terminal| terminal.id() == id) {
47 handles.remove(index);
48 cx.notify();
49 }
50 })
51 .detach();
52
53 let setting = settings::get::<TerminalSettings>(cx);
54
55 if setting.automatically_activate_python_virtual_environment {
56 self.set_up_python_virtual_environment(&terminal_handle, cx);
57 }
58
59 terminal_handle
60 });
61
62 terminal
63 }
64 }
65
66 fn set_up_python_virtual_environment(
67 &mut self,
68 terminal_handle: &ModelHandle<Terminal>,
69 cx: &mut ModelContext<Project>,
70 ) {
71 let virtual_environment = self.find_python_virtual_environment(cx);
72 if let Some(virtual_environment) = virtual_environment {
73 // Paths are not strings so we need to jump through some hoops to format the command without `format!`
74 let mut command = Vec::from("source ".as_bytes());
75 command.extend_from_slice(virtual_environment.as_os_str().as_bytes());
76 command.push(b'\n');
77
78 terminal_handle.update(cx, |this, _| this.input_bytes(command));
79 }
80 }
81
82 pub fn find_python_virtual_environment(
83 &mut self,
84 cx: &mut ModelContext<Project>,
85 ) -> Option<PathBuf> {
86 const VIRTUAL_ENVIRONMENT_NAMES: [&str; 4] = [".env", "env", ".venv", "venv"];
87
88 let worktree_paths = self
89 .worktrees(cx)
90 .map(|worktree| worktree.read(cx).abs_path());
91
92 for worktree_path in worktree_paths {
93 for virtual_environment_name in VIRTUAL_ENVIRONMENT_NAMES {
94 let mut path = worktree_path.join(virtual_environment_name);
95 path.push("bin/activate");
96
97 if path.exists() {
98 return Some(path);
99 }
100 }
101 }
102
103 None
104 }
105
106 pub fn local_terminal_handles(&self) -> &Vec<WeakModelHandle<terminal::Terminal>> {
107 &self.terminals.local_handles
108 }
109}
110
111// TODO: Add a few tests for adding and removing terminal tabs