Detailed changes
@@ -284,8 +284,6 @@
// "directory": "~/zed/projects/"
// }
// }
- //
- //
"working_directory": "current_project_directory",
// Set the cursor blinking behavior in the terminal.
// May take 4 values:
@@ -334,13 +332,32 @@
// "line_height": {
// "custom": 2
// },
- "line_height": "comfortable"
+ "line_height": "comfortable",
+ // Activate the python virtual environment, if one is found, in the
+ // terminal's working directory (as resolved by the working_directory
+ // setting). Set this to "off" to disable this behavior.
+ "detect_venv": {
+ "on": {
+ // Default directories to search for virtual environments, relative
+ // to the current working directory. We recommend overriding this
+ // in your project's settings, rather than globally.
+ "directories": [
+ ".env",
+ "env",
+ ".venv",
+ "venv"
+ ],
+ // Can also be 'csh' and 'fish'
+ "activate_script": "default"
+ }
+ }
// Set the terminal's font size. If this option is not included,
// the terminal will default to matching the buffer's font size.
- // "font_size": "15"
+ // "font_size": "15",
// Set the terminal's font family. If this option is not included,
// the terminal will default to matching the buffer's font family.
- // "font_family": "Zed Mono"
+ // "font_family": "Zed Mono",
+ // ---
},
// Difference settings for semantic_index
"semantic_index": {
@@ -1,7 +1,13 @@
use crate::Project;
use gpui::{AnyWindowHandle, ModelContext, ModelHandle, WeakModelHandle};
-use std::path::PathBuf;
-use terminal::{Terminal, TerminalBuilder, TerminalSettings};
+use std::path::{Path, PathBuf};
+use terminal::{
+ terminal_settings::{self, TerminalSettings, VenvSettingsContent},
+ Terminal, TerminalBuilder,
+};
+
+#[cfg(target_os = "macos")]
+use std::os::unix::ffi::OsStrExt;
pub struct Terminals {
pub(crate) local_handles: Vec<WeakModelHandle<terminal::Terminal>>,
@@ -20,10 +26,12 @@ impl Project {
));
} else {
let settings = settings::get::<TerminalSettings>(cx);
+ let python_settings = settings.detect_venv.clone();
+ let shell = settings.shell.clone();
let terminal = TerminalBuilder::new(
working_directory.clone(),
- settings.shell.clone(),
+ shell.clone(),
settings.env.clone(),
Some(settings.blinking.clone()),
settings.alternate_scroll,
@@ -47,6 +55,15 @@ impl Project {
})
.detach();
+ if let Some(python_settings) = &python_settings.as_option() {
+ let activate_script_path =
+ self.find_activate_script_path(&python_settings, working_directory);
+ self.activate_python_virtual_environment(
+ activate_script_path,
+ &terminal_handle,
+ cx,
+ );
+ }
terminal_handle
});
@@ -54,6 +71,50 @@ impl Project {
}
}
+ pub fn find_activate_script_path(
+ &mut self,
+ settings: &VenvSettingsContent,
+ working_directory: Option<PathBuf>,
+ ) -> Option<PathBuf> {
+ // When we are unable to resolve the working directory, the terminal builder
+ // defaults to '/'. We should probably encode this directly somewhere, but for
+ // now, let's just hard code it here.
+ let working_directory = working_directory.unwrap_or_else(|| Path::new("/").to_path_buf());
+ let activate_script_name = match settings.activate_script {
+ terminal_settings::ActivateScript::Default => "activate",
+ terminal_settings::ActivateScript::Csh => "activate.csh",
+ terminal_settings::ActivateScript::Fish => "activate.fish",
+ };
+
+ for virtual_environment_name in settings.directories {
+ let mut path = working_directory.join(virtual_environment_name);
+ path.push("bin/");
+ path.push(activate_script_name);
+
+ if path.exists() {
+ return Some(path);
+ }
+ }
+
+ None
+ }
+
+ fn activate_python_virtual_environment(
+ &mut self,
+ activate_script: Option<PathBuf>,
+ terminal_handle: &ModelHandle<Terminal>,
+ cx: &mut ModelContext<Project>,
+ ) {
+ if let Some(activate_script) = activate_script {
+ // Paths are not strings so we need to jump through some hoops to format the command without `format!`
+ let mut command = Vec::from("source ".as_bytes());
+ command.extend_from_slice(activate_script.as_os_str().as_bytes());
+ command.push(b'\n');
+
+ terminal_handle.update(cx, |this, _| this.input_bytes(command));
+ }
+ }
+
pub fn local_terminal_handles(&self) -> &Vec<WeakModelHandle<terminal::Terminal>> {
&self.terminals.local_handles
}
@@ -1,5 +1,6 @@
pub mod mappings;
pub use alacritty_terminal;
+pub mod terminal_settings;
use alacritty_terminal::{
ansi::{ClearMode, Handler},
@@ -31,8 +32,8 @@ use mappings::mouse::{
};
use procinfo::LocalProcessInfo;
-use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
+use terminal_settings::{AlternateScroll, Shell, TerminalBlink, TerminalSettings};
use util::truncate_and_trailoff;
use std::{
@@ -48,7 +49,6 @@ use std::{
use thiserror::Error;
use gpui::{
- fonts,
geometry::vector::{vec2f, Vector2F},
keymap_matcher::Keystroke,
platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase},
@@ -134,122 +134,6 @@ pub fn init(cx: &mut AppContext) {
settings::register::<TerminalSettings>(cx);
}
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalDockPosition {
- Left,
- Bottom,
- Right,
-}
-
-#[derive(Deserialize)]
-pub struct TerminalSettings {
- pub shell: Shell,
- pub working_directory: WorkingDirectory,
- font_size: Option<f32>,
- pub font_family: Option<String>,
- pub line_height: TerminalLineHeight,
- pub font_features: Option<fonts::Features>,
- pub env: HashMap<String, String>,
- pub blinking: TerminalBlink,
- pub alternate_scroll: AlternateScroll,
- pub option_as_meta: bool,
- pub copy_on_select: bool,
- pub dock: TerminalDockPosition,
- pub default_width: f32,
- pub default_height: f32,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct TerminalSettingsContent {
- pub shell: Option<Shell>,
- pub working_directory: Option<WorkingDirectory>,
- pub font_size: Option<f32>,
- pub font_family: Option<String>,
- pub line_height: Option<TerminalLineHeight>,
- pub font_features: Option<fonts::Features>,
- pub env: Option<HashMap<String, String>>,
- pub blinking: Option<TerminalBlink>,
- pub alternate_scroll: Option<AlternateScroll>,
- pub option_as_meta: Option<bool>,
- pub copy_on_select: Option<bool>,
- pub dock: Option<TerminalDockPosition>,
- pub default_width: Option<f32>,
- pub default_height: Option<f32>,
-}
-
-impl TerminalSettings {
- pub fn font_size(&self, cx: &AppContext) -> Option<f32> {
- self.font_size
- .map(|size| theme::adjusted_font_size(size, cx))
- }
-}
-
-impl settings::Setting for TerminalSettings {
- const KEY: Option<&'static str> = Some("terminal");
-
- type FileContent = TerminalSettingsContent;
-
- fn load(
- default_value: &Self::FileContent,
- user_values: &[&Self::FileContent],
- _: &AppContext,
- ) -> Result<Self> {
- Self::load_via_json_merge(default_value, user_values)
- }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalLineHeight {
- #[default]
- Comfortable,
- Standard,
- Custom(f32),
-}
-
-impl TerminalLineHeight {
- pub fn value(&self) -> f32 {
- match self {
- TerminalLineHeight::Comfortable => 1.618,
- TerminalLineHeight::Standard => 1.3,
- TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
- }
- }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalBlink {
- Off,
- TerminalControlled,
- On,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum Shell {
- System,
- Program(String),
- WithArguments { program: String, args: Vec<String> },
-}
-
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum AlternateScroll {
- On,
- Off,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum WorkingDirectory {
- CurrentProjectDirectory,
- FirstProjectDirectory,
- AlwaysHome,
- Always { directory: String },
-}
-
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct TerminalSize {
pub cell_width: f32,
@@ -1018,6 +902,10 @@ impl Terminal {
self.pty_tx.notify(input.into_bytes());
}
+ fn write_bytes_to_pty(&self, input: Vec<u8>) {
+ self.pty_tx.notify(input);
+ }
+
pub fn input(&mut self, input: String) {
self.events
.push_back(InternalEvent::Scroll(AlacScroll::Bottom));
@@ -1026,6 +914,14 @@ impl Terminal {
self.write_to_pty(input);
}
+ pub fn input_bytes(&mut self, input: Vec<u8>) {
+ self.events
+ .push_back(InternalEvent::Scroll(AlacScroll::Bottom));
+ self.events.push_back(InternalEvent::SetSelection(None));
+
+ self.write_bytes_to_pty(input);
+ }
+
pub fn try_keystroke(&mut self, keystroke: &Keystroke, alt_is_meta: bool) -> bool {
let esc = to_esc_str(keystroke, &self.last_content.mode, alt_is_meta);
if let Some(esc) = esc {
@@ -0,0 +1,163 @@
+use std::{collections::HashMap, path::PathBuf};
+
+use gpui::{fonts, AppContext};
+use schemars::JsonSchema;
+use serde_derive::{Deserialize, Serialize};
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub enum TerminalDockPosition {
+ Left,
+ Bottom,
+ Right,
+}
+
+#[derive(Deserialize)]
+pub struct TerminalSettings {
+ pub shell: Shell,
+ pub working_directory: WorkingDirectory,
+ font_size: Option<f32>,
+ pub font_family: Option<String>,
+ pub line_height: TerminalLineHeight,
+ pub font_features: Option<fonts::Features>,
+ pub env: HashMap<String, String>,
+ pub blinking: TerminalBlink,
+ pub alternate_scroll: AlternateScroll,
+ pub option_as_meta: bool,
+ pub copy_on_select: bool,
+ pub dock: TerminalDockPosition,
+ pub default_width: f32,
+ pub default_height: f32,
+ pub detect_venv: VenvSettings,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum VenvSettings {
+ #[default]
+ Off,
+ On {
+ activate_script: Option<ActivateScript>,
+ directories: Option<Vec<PathBuf>>,
+ },
+}
+
+pub struct VenvSettingsContent<'a> {
+ pub activate_script: ActivateScript,
+ pub directories: &'a [PathBuf],
+}
+
+impl VenvSettings {
+ pub fn as_option(&self) -> Option<VenvSettingsContent> {
+ match self {
+ VenvSettings::Off => None,
+ VenvSettings::On {
+ activate_script,
+ directories,
+ } => Some(VenvSettingsContent {
+ activate_script: activate_script.unwrap_or(ActivateScript::Default),
+ directories: directories.as_deref().unwrap_or(&[]),
+ }),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ActivateScript {
+ #[default]
+ Default,
+ Csh,
+ Fish,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+pub struct TerminalSettingsContent {
+ pub shell: Option<Shell>,
+ pub working_directory: Option<WorkingDirectory>,
+ pub font_size: Option<f32>,
+ pub font_family: Option<String>,
+ pub line_height: Option<TerminalLineHeight>,
+ pub font_features: Option<fonts::Features>,
+ pub env: Option<HashMap<String, String>>,
+ pub blinking: Option<TerminalBlink>,
+ pub alternate_scroll: Option<AlternateScroll>,
+ pub option_as_meta: Option<bool>,
+ pub copy_on_select: Option<bool>,
+ pub dock: Option<TerminalDockPosition>,
+ pub default_width: Option<f32>,
+ pub default_height: Option<f32>,
+ pub detect_venv: Option<VenvSettings>,
+}
+
+impl TerminalSettings {
+ pub fn font_size(&self, cx: &AppContext) -> Option<f32> {
+ self.font_size
+ .map(|size| theme::adjusted_font_size(size, cx))
+ }
+}
+
+impl settings::Setting for TerminalSettings {
+ const KEY: Option<&'static str> = Some("terminal");
+
+ type FileContent = TerminalSettingsContent;
+
+ fn load(
+ default_value: &Self::FileContent,
+ user_values: &[&Self::FileContent],
+ _: &AppContext,
+ ) -> anyhow::Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
+#[serde(rename_all = "snake_case")]
+pub enum TerminalLineHeight {
+ #[default]
+ Comfortable,
+ Standard,
+ Custom(f32),
+}
+
+impl TerminalLineHeight {
+ pub fn value(&self) -> f32 {
+ match self {
+ TerminalLineHeight::Comfortable => 1.618,
+ TerminalLineHeight::Standard => 1.3,
+ TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum TerminalBlink {
+ Off,
+ TerminalControlled,
+ On,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum Shell {
+ System,
+ Program(String),
+ WithArguments { program: String, args: Vec<String> },
+}
+
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum AlternateScroll {
+ On,
+ Off,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum WorkingDirectory {
+ CurrentProjectDirectory,
+ FirstProjectDirectory,
+ AlwaysHome,
+ Always { directory: String },
+}
@@ -25,7 +25,8 @@ use terminal::{
term::{cell::Flags, TermMode},
},
mappings::colors::convert_color,
- IndexedCell, Terminal, TerminalContent, TerminalSettings, TerminalSize,
+ terminal_settings::TerminalSettings,
+ IndexedCell, Terminal, TerminalContent, TerminalSize,
};
use theme::{TerminalStyle, ThemeSettings};
use util::ResultExt;
@@ -9,7 +9,7 @@ use gpui::{
use project::Fs;
use serde::{Deserialize, Serialize};
use settings::SettingsStore;
-use terminal::{TerminalDockPosition, TerminalSettings};
+use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings};
use util::{ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel},
@@ -33,7 +33,8 @@ use terminal::{
index::Point,
term::{search::RegexSearch, TermMode},
},
- Event, MaybeNavigationTarget, Terminal, TerminalBlink, WorkingDirectory,
+ terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory},
+ Event, MaybeNavigationTarget, Terminal,
};
use util::{paths::PathLikeWithPosition, ResultExt};
use workspace::{
@@ -44,8 +45,6 @@ use workspace::{
NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
};
-pub use terminal::TerminalSettings;
-
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
///Event to transmit the scroll from the element to the view