Add support for profiles (#131)
This commit is contained in:
parent
8fd3197cc4
commit
3a3e42110c
10 changed files with 508 additions and 100 deletions
|
|
@ -10,6 +10,8 @@ use serde::{Deserialize, Serialize};
|
|||
use std::collections::BTreeMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::fl;
|
||||
|
||||
pub const CONFIG_VERSION: u64 = 1;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
|
|
@ -29,6 +31,35 @@ impl AppTheme {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProfileId(pub u64);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct Profile {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub command: String,
|
||||
#[serde(default)]
|
||||
pub syntax_theme_dark: String,
|
||||
#[serde(default)]
|
||||
pub syntax_theme_light: String,
|
||||
#[serde(default)]
|
||||
pub tab_title: String,
|
||||
}
|
||||
|
||||
impl Default for Profile {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: fl!("new-profile"),
|
||||
command: String::new(),
|
||||
syntax_theme_dark: "COSMIC Dark".to_string(),
|
||||
syntax_theme_light: "COSMIC Light".to_string(),
|
||||
tab_title: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct Config {
|
||||
pub app_theme: AppTheme,
|
||||
|
|
@ -39,6 +70,7 @@ pub struct Config {
|
|||
pub bold_font_weight: u16,
|
||||
pub font_stretch: u16,
|
||||
pub font_size_zoom_step_mul_100: u16,
|
||||
pub profiles: BTreeMap<ProfileId, Profile>,
|
||||
pub show_headerbar: bool,
|
||||
pub use_bright_bold: bool,
|
||||
pub syntax_theme_dark: String,
|
||||
|
|
@ -50,18 +82,19 @@ impl Default for Config {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
app_theme: AppTheme::System,
|
||||
bold_font_weight: Weight::BOLD.0,
|
||||
dim_font_weight: Weight::NORMAL.0,
|
||||
focus_follow_mouse: false,
|
||||
font_name: "Fira Mono".to_string(),
|
||||
font_size: 14,
|
||||
font_weight: Weight::NORMAL.0,
|
||||
dim_font_weight: Weight::NORMAL.0,
|
||||
bold_font_weight: Weight::BOLD.0,
|
||||
font_stretch: Stretch::Normal.to_number(),
|
||||
font_size_zoom_step_mul_100: 100,
|
||||
font_stretch: Stretch::Normal.to_number(),
|
||||
font_weight: Weight::NORMAL.0,
|
||||
profiles: BTreeMap::new(),
|
||||
show_headerbar: true,
|
||||
use_bright_bold: false,
|
||||
syntax_theme_dark: "COSMIC Dark".to_string(),
|
||||
syntax_theme_light: "COSMIC Light".to_string(),
|
||||
focus_follow_mouse: false,
|
||||
use_bright_bold: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -81,13 +114,42 @@ impl Config {
|
|||
Metrics::new(font_size, line_height)
|
||||
}
|
||||
|
||||
// Get a sorted and adjusted for duplicates list of profiles names and ids
|
||||
pub fn profile_names(&self) -> Vec<(String, ProfileId)> {
|
||||
let mut profile_names = Vec::<(String, ProfileId)>::with_capacity(self.profiles.len());
|
||||
for (profile_id, profile) in self.profiles.iter() {
|
||||
let mut name = profile.name.clone();
|
||||
|
||||
let mut copies = 1;
|
||||
while profile_names.iter().find(|x| x.0 == name).is_some() {
|
||||
copies += 1;
|
||||
name = format!("{} ({})", profile.name, copies);
|
||||
}
|
||||
|
||||
profile_names.push((name, *profile_id));
|
||||
}
|
||||
profile_names.sort_by(|a, b| lexical_sort::natural_lexical_cmp(&a.0, &b.0));
|
||||
profile_names
|
||||
}
|
||||
|
||||
// Get current syntax theme based on dark mode
|
||||
pub fn syntax_theme(&self) -> &str {
|
||||
pub fn syntax_theme(&self, profile_id_opt: Option<ProfileId>) -> &str {
|
||||
let dark = self.app_theme.theme().theme_type.is_dark();
|
||||
if dark {
|
||||
&self.syntax_theme_dark
|
||||
} else {
|
||||
&self.syntax_theme_light
|
||||
match profile_id_opt.and_then(|profile_id| self.profiles.get(&profile_id)) {
|
||||
Some(profile) => {
|
||||
if dark {
|
||||
&profile.syntax_theme_dark
|
||||
} else {
|
||||
&profile.syntax_theme_light
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if dark {
|
||||
&self.syntax_theme_dark
|
||||
} else {
|
||||
&self.syntax_theme_light
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ impl IconCache {
|
|||
}
|
||||
|
||||
bundle!("edit-clear-symbolic", 16);
|
||||
bundle!("edit-delete-symbolic", 16);
|
||||
bundle!("list-add-symbolic", 16);
|
||||
bundle!("go-down-symbolic", 16);
|
||||
bundle!("go-up-symbolic", 16);
|
||||
bundle!("window-close-symbolic", 16);
|
||||
|
|
|
|||
362
src/main.rs
362
src/main.rs
|
|
@ -6,7 +6,7 @@ use alacritty_terminal::{
|
|||
};
|
||||
use cosmic::{
|
||||
app::{message, Command, Core, Settings},
|
||||
cosmic_config::{self, CosmicConfigEntry},
|
||||
cosmic_config::{self, ConfigSet, CosmicConfigEntry},
|
||||
cosmic_theme, executor,
|
||||
iced::{
|
||||
advanced::graphics::text::font_system,
|
||||
|
|
@ -29,7 +29,7 @@ use std::{
|
|||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use config::{AppTheme, Config, CONFIG_VERSION};
|
||||
use config::{AppTheme, Config, Profile, ProfileId, CONFIG_VERSION};
|
||||
mod config;
|
||||
mod mouse_reporter;
|
||||
|
||||
|
|
@ -175,6 +175,8 @@ pub enum Action {
|
|||
PaneSplitVertical,
|
||||
PaneToggleMaximized,
|
||||
Paste,
|
||||
ProfileOpen(ProfileId),
|
||||
Profiles,
|
||||
SelectAll,
|
||||
Settings,
|
||||
ShowHeaderBar(bool),
|
||||
|
|
@ -211,6 +213,8 @@ impl Action {
|
|||
Action::PaneSplitVertical => Message::PaneSplit(pane_grid::Axis::Vertical),
|
||||
Action::PaneToggleMaximized => Message::PaneToggleMaximized,
|
||||
Action::Paste => Message::Paste(entity_opt),
|
||||
Action::ProfileOpen(profile_id) => Message::ProfileOpen(profile_id),
|
||||
Action::Profiles => Message::ToggleContextPage(ContextPage::Profiles),
|
||||
Action::SelectAll => Message::SelectAll(entity_opt),
|
||||
Action::Settings => Message::ToggleContextPage(ContextPage::Settings),
|
||||
Action::ShowHeaderBar(show_headerbar) => Message::ShowHeaderBar(show_headerbar),
|
||||
|
|
@ -264,6 +268,15 @@ pub enum Message {
|
|||
MouseEnter(pane_grid::Pane),
|
||||
Paste(Option<segmented_button::Entity>),
|
||||
PasteValue(Option<segmented_button::Entity>, String),
|
||||
ProfileCollapse(ProfileId),
|
||||
ProfileCommand(ProfileId, String),
|
||||
ProfileExpand(ProfileId),
|
||||
ProfileName(ProfileId, String),
|
||||
ProfileNew,
|
||||
ProfileOpen(ProfileId),
|
||||
ProfileRemove(ProfileId),
|
||||
ProfileSyntaxTheme(ProfileId, usize, bool),
|
||||
ProfileTabTitle(ProfileId, String),
|
||||
SelectAll(Option<segmented_button::Entity>),
|
||||
UseBrightBold(bool),
|
||||
ShowHeaderBar(bool),
|
||||
|
|
@ -291,12 +304,14 @@ pub enum Message {
|
|||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ContextPage {
|
||||
Profiles,
|
||||
Settings,
|
||||
}
|
||||
|
||||
impl ContextPage {
|
||||
fn title(&self) -> String {
|
||||
match self {
|
||||
Self::Profiles => fl!("profiles"),
|
||||
Self::Settings => fl!("settings"),
|
||||
}
|
||||
}
|
||||
|
|
@ -333,6 +348,7 @@ pub struct App {
|
|||
term_event_tx_opt: Option<mpsc::Sender<(pane_grid::Pane, segmented_button::Entity, TermEvent)>>,
|
||||
startup_options: Option<tty::Options>,
|
||||
term_config: TermConfig,
|
||||
profile_expanded: Option<ProfileId>,
|
||||
show_advanced_font_settings: bool,
|
||||
modifiers: Modifiers,
|
||||
}
|
||||
|
|
@ -367,6 +383,19 @@ impl App {
|
|||
self.update_config()
|
||||
}
|
||||
|
||||
fn save_profiles(&mut self) -> Command<Message> {
|
||||
// Optimized for just saving profiles
|
||||
if let Some(ref config_handler) = self.config_handler {
|
||||
match config_handler.set("profiles", &self.config.profiles) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::error!("failed to save config: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn update_focus(&self) -> Command<Message> {
|
||||
if self.find {
|
||||
widget::text_input::focus(self.find_search_id.clone())
|
||||
|
|
@ -476,6 +505,147 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
fn profiles(&self) -> Element<Message> {
|
||||
let cosmic_theme::Spacing {
|
||||
space_s,
|
||||
space_xs,
|
||||
space_xxs,
|
||||
space_xxxs,
|
||||
..
|
||||
} = self.core().system_theme().cosmic().spacing;
|
||||
|
||||
let mut sections = Vec::with_capacity(2);
|
||||
|
||||
if !self.config.profiles.is_empty() {
|
||||
let mut profiles_section = widget::settings::view_section("");
|
||||
for (profile_name, profile_id) in self.config.profile_names() {
|
||||
let profile = match self.config.profiles.get(&profile_id) {
|
||||
Some(some) => some,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let expanded = self.profile_expanded == Some(profile_id);
|
||||
|
||||
profiles_section = profiles_section.add(
|
||||
widget::settings::item::builder(profile_name).control(
|
||||
widget::row::with_children(vec![
|
||||
widget::button(icon_cache_get("edit-delete-symbolic", 16))
|
||||
.on_press(Message::ProfileRemove(profile_id))
|
||||
.style(style::Button::Icon)
|
||||
.into(),
|
||||
if expanded {
|
||||
widget::button(icon_cache_get("go-up-symbolic", 16))
|
||||
.on_press(Message::ProfileCollapse(profile_id))
|
||||
} else {
|
||||
widget::button(icon_cache_get("go-down-symbolic", 16))
|
||||
.on_press(Message::ProfileExpand(profile_id))
|
||||
}
|
||||
.style(style::Button::Icon)
|
||||
.into(),
|
||||
])
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(space_xxs),
|
||||
),
|
||||
);
|
||||
|
||||
if expanded {
|
||||
let dark_selected = self
|
||||
.theme_names
|
||||
.iter()
|
||||
.position(|theme_name| theme_name == &profile.syntax_theme_dark);
|
||||
let light_selected = self
|
||||
.theme_names
|
||||
.iter()
|
||||
.position(|theme_name| theme_name == &profile.syntax_theme_light);
|
||||
|
||||
let expanded_section = widget::settings::view_section("")
|
||||
.add(
|
||||
widget::column::with_children(vec![
|
||||
widget::column::with_children(vec![
|
||||
widget::text(fl!("name")).into(),
|
||||
widget::text_input("", &profile.name)
|
||||
.on_input(move |text| {
|
||||
Message::ProfileName(profile_id, text)
|
||||
})
|
||||
.into(),
|
||||
])
|
||||
.spacing(space_xxxs)
|
||||
.into(),
|
||||
widget::column::with_children(vec![
|
||||
widget::text(fl!("command-line")).into(),
|
||||
widget::text_input("", &profile.command)
|
||||
.on_input(move |text| {
|
||||
Message::ProfileCommand(profile_id, text)
|
||||
})
|
||||
.into(),
|
||||
widget::text::caption(fl!("command-line-description")).into(),
|
||||
])
|
||||
.spacing(space_xxxs)
|
||||
.into(),
|
||||
widget::column::with_children(vec![
|
||||
widget::text(fl!("tab-title")).into(),
|
||||
widget::text_input("", &profile.tab_title)
|
||||
.on_input(move |text| {
|
||||
Message::ProfileTabTitle(profile_id, text)
|
||||
})
|
||||
.into(),
|
||||
widget::text::caption(fl!("tab-title-description")).into(),
|
||||
])
|
||||
.spacing(space_xxxs)
|
||||
.into(),
|
||||
])
|
||||
.padding([0, space_s])
|
||||
.spacing(space_xs),
|
||||
)
|
||||
.add(
|
||||
//TODO: rename to color-scheme-dark?
|
||||
widget::settings::item::builder(fl!("syntax-dark")).control(
|
||||
widget::dropdown(
|
||||
&self.theme_names,
|
||||
dark_selected,
|
||||
move |theme_i| {
|
||||
Message::ProfileSyntaxTheme(profile_id, theme_i, true)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
//TODO: rename to color-scheme-light?
|
||||
widget::settings::item::builder(fl!("syntax-light")).control(
|
||||
widget::dropdown(
|
||||
&self.theme_names,
|
||||
light_selected,
|
||||
move |theme_i| {
|
||||
Message::ProfileSyntaxTheme(profile_id, theme_i, false)
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
let padding = Padding {
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
left: space_s as f32,
|
||||
right: space_s as f32,
|
||||
};
|
||||
profiles_section =
|
||||
profiles_section.add(widget::container(expanded_section).padding(padding))
|
||||
}
|
||||
}
|
||||
sections.push(profiles_section.into());
|
||||
}
|
||||
|
||||
let add_profile = widget::row::with_children(vec![
|
||||
widget::horizontal_space(Length::Fill).into(),
|
||||
widget::button(widget::text(fl!("add-profile")))
|
||||
.on_press(Message::ProfileNew)
|
||||
.into(),
|
||||
]);
|
||||
sections.push(add_profile.into());
|
||||
|
||||
widget::settings::view_column(sections).into()
|
||||
}
|
||||
|
||||
fn settings(&self) -> Element<Message> {
|
||||
let app_theme_selected = match self.config.app_theme {
|
||||
AppTheme::Dark => 1,
|
||||
|
|
@ -658,48 +828,85 @@ impl App {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn create_and_focus_new_terminal(&mut self, pane: pane_grid::Pane) {
|
||||
fn create_and_focus_new_terminal(
|
||||
&mut self,
|
||||
pane: pane_grid::Pane,
|
||||
profile_id_opt: Option<ProfileId>,
|
||||
) -> Command<Message> {
|
||||
self.pane_model.focus = pane;
|
||||
match &self.term_event_tx_opt {
|
||||
Some(term_event_tx) => match self.themes.get(self.config.syntax_theme()) {
|
||||
Some(colors) => {
|
||||
let current_pane = self.pane_model.focus;
|
||||
if let Some(tab_model) = self.pane_model.active_mut() {
|
||||
let entity = tab_model
|
||||
.insert()
|
||||
.text("New Terminal")
|
||||
.closable()
|
||||
.activate()
|
||||
.id();
|
||||
// Use the startup options, or defaults
|
||||
let options = self.startup_options.take().unwrap_or_default();
|
||||
let mut terminal = Terminal::new(
|
||||
current_pane,
|
||||
entity,
|
||||
term_event_tx.clone(),
|
||||
self.term_config.clone(),
|
||||
options,
|
||||
&self.config,
|
||||
*colors,
|
||||
Some(term_event_tx) => {
|
||||
match self.themes.get(self.config.syntax_theme(profile_id_opt)) {
|
||||
Some(colors) => {
|
||||
let current_pane = self.pane_model.focus;
|
||||
if let Some(tab_model) = self.pane_model.active_mut() {
|
||||
let entity = tab_model
|
||||
.insert()
|
||||
.text("New Terminal")
|
||||
.closable()
|
||||
.activate()
|
||||
.id();
|
||||
// Use the profile options, startup options, or defaults
|
||||
let options = match profile_id_opt
|
||||
.and_then(|profile_id| self.config.profiles.get(&profile_id))
|
||||
{
|
||||
Some(profile) => {
|
||||
let mut shell = None;
|
||||
if let Some(mut args) = shlex::split(&profile.command) {
|
||||
if !args.is_empty() {
|
||||
let command = args.remove(0);
|
||||
shell = Some(tty::Shell::new(command, args));
|
||||
}
|
||||
}
|
||||
tty::Options {
|
||||
shell,
|
||||
//TODO: configurable working directory?
|
||||
working_directory: None,
|
||||
//TODO: configurable hold (keep open when child exits)?
|
||||
hold: false,
|
||||
}
|
||||
}
|
||||
None => self.startup_options.take().unwrap_or_default(),
|
||||
};
|
||||
match Terminal::new(
|
||||
current_pane,
|
||||
entity,
|
||||
term_event_tx.clone(),
|
||||
self.term_config.clone(),
|
||||
options,
|
||||
&self.config,
|
||||
*colors,
|
||||
profile_id_opt,
|
||||
) {
|
||||
Ok(mut terminal) => {
|
||||
terminal.set_config(&self.config, &self.themes, self.zoom_adj);
|
||||
tab_model
|
||||
.data_set::<Mutex<Terminal>>(entity, Mutex::new(terminal));
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to open terminal: {}", err);
|
||||
// Clean up partially created tab
|
||||
return self.update(Message::TabClose(Some(entity)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Found no active pane");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::error!(
|
||||
"failed to find terminal theme {:?}",
|
||||
self.config.syntax_theme(profile_id_opt)
|
||||
);
|
||||
terminal.set_config(&self.config, &self.themes, self.zoom_adj);
|
||||
tab_model.data_set::<Mutex<Terminal>>(entity, Mutex::new(terminal));
|
||||
} else {
|
||||
log::error!("Found no active pane");
|
||||
//TODO: fall back to known good theme
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::error!(
|
||||
"failed to find terminal theme {:?}",
|
||||
self.config.syntax_theme()
|
||||
);
|
||||
//TODO: fall back to known good theme
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
log::warn!("tried to create new tab before having event channel");
|
||||
}
|
||||
}
|
||||
return self.update_title(Some(pane));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -864,6 +1071,7 @@ impl Application for App {
|
|||
startup_options: flags.startup_options,
|
||||
term_config: flags.term_config,
|
||||
term_event_tx_opt: None,
|
||||
profile_expanded: None,
|
||||
show_advanced_font_settings: false,
|
||||
modifiers: Modifiers::empty(),
|
||||
};
|
||||
|
|
@ -1096,9 +1304,9 @@ impl Application for App {
|
|||
);
|
||||
if let Some((pane, _)) = result {
|
||||
self.terminal_ids.insert(pane, widget::Id::unique());
|
||||
self.create_and_focus_new_terminal(pane);
|
||||
let command = self.create_and_focus_new_terminal(pane, None);
|
||||
self.pane_model.panes_created += 1;
|
||||
return self.update_title(Some(pane));
|
||||
return command;
|
||||
}
|
||||
}
|
||||
Message::PaneToggleMaximized => {
|
||||
|
|
@ -1142,6 +1350,77 @@ impl Application for App {
|
|||
}
|
||||
return self.update_focus();
|
||||
}
|
||||
Message::ProfileCollapse(_profile_id) => {
|
||||
self.profile_expanded = None;
|
||||
}
|
||||
Message::ProfileCommand(profile_id, text) => {
|
||||
if let Some(profile) = self.config.profiles.get_mut(&profile_id) {
|
||||
profile.command = text;
|
||||
return self.save_profiles();
|
||||
}
|
||||
}
|
||||
Message::ProfileExpand(profile_id) => {
|
||||
self.profile_expanded = Some(profile_id);
|
||||
}
|
||||
Message::ProfileName(profile_id, text) => {
|
||||
if let Some(profile) = self.config.profiles.get_mut(&profile_id) {
|
||||
profile.name = text;
|
||||
return self.save_profiles();
|
||||
}
|
||||
}
|
||||
Message::ProfileNew => {
|
||||
// Get next profile ID
|
||||
let profile_id = self
|
||||
.config
|
||||
.profiles
|
||||
.last_key_value()
|
||||
.map(|(id, _)| ProfileId(id.0 + 1))
|
||||
.unwrap_or_default();
|
||||
self.config.profiles.insert(profile_id, Profile::default());
|
||||
self.profile_expanded = Some(profile_id);
|
||||
return self.save_profiles();
|
||||
}
|
||||
Message::ProfileOpen(profile_id) => {
|
||||
return self.create_and_focus_new_terminal(self.pane_model.focus, Some(profile_id));
|
||||
}
|
||||
Message::ProfileRemove(profile_id) => {
|
||||
// Reset matching terminals to default profile
|
||||
for (_pane, tab_model) in self.pane_model.panes.iter() {
|
||||
for entity in tab_model.iter() {
|
||||
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
|
||||
let mut terminal = terminal.lock().unwrap();
|
||||
if terminal.profile_id_opt == Some(profile_id) {
|
||||
terminal.profile_id_opt = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.config.profiles.remove(&profile_id);
|
||||
return self.save_profiles();
|
||||
}
|
||||
Message::ProfileSyntaxTheme(profile_id, theme_i, dark) => {
|
||||
match self.theme_names.get(theme_i) {
|
||||
Some(theme_name) => {
|
||||
if let Some(profile) = self.config.profiles.get_mut(&profile_id) {
|
||||
if dark {
|
||||
profile.syntax_theme_dark = theme_name.to_string();
|
||||
} else {
|
||||
profile.syntax_theme_light = theme_name.to_string();
|
||||
}
|
||||
return self.save_profiles();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("failed to find syntax theme with index {}", theme_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ProfileTabTitle(profile_id, text) => {
|
||||
if let Some(profile) = self.config.profiles.get_mut(&profile_id) {
|
||||
profile.tab_title = text;
|
||||
return self.save_profiles();
|
||||
}
|
||||
}
|
||||
Message::SelectAll(entity_opt) => {
|
||||
if let Some(tab_model) = self.pane_model.active() {
|
||||
let entity = entity_opt.unwrap_or_else(|| tab_model.active());
|
||||
|
|
@ -1280,7 +1559,9 @@ impl Application for App {
|
|||
self.pane_model.focus = pane;
|
||||
return self.update_title(Some(pane));
|
||||
}
|
||||
Message::TabNew => self.create_and_focus_new_terminal(self.pane_model.focus),
|
||||
Message::TabNew => {
|
||||
return self.create_and_focus_new_terminal(self.pane_model.focus, None)
|
||||
}
|
||||
Message::TabNext => {
|
||||
if let Some(tab_model) = self.pane_model.active() {
|
||||
let len = tab_model.iter().count();
|
||||
|
|
@ -1449,17 +1730,18 @@ impl Application for App {
|
|||
}
|
||||
|
||||
Some(match self.context_page {
|
||||
ContextPage::Profiles => self.profiles(),
|
||||
ContextPage::Settings => self.settings(),
|
||||
})
|
||||
}
|
||||
|
||||
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
||||
vec![menu_bar(&self.key_binds)]
|
||||
vec![menu_bar(&self.config, &self.key_binds)]
|
||||
}
|
||||
|
||||
fn header_end(&self) -> Vec<Element<Self::Message>> {
|
||||
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
|
||||
vec![widget::button(widget::icon::from_name("list-add-symbolic"))
|
||||
vec![widget::button(icon_cache_get("list-add-symbolic", 16))
|
||||
.on_press(Message::TabNew)
|
||||
.padding(space_xxs)
|
||||
.style(style::Button::Icon)
|
||||
|
|
|
|||
14
src/menu.rs
14
src/menu.rs
|
|
@ -108,7 +108,7 @@ pub fn context_menu<'a>(
|
|||
.into()
|
||||
}
|
||||
|
||||
pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
|
||||
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
|
||||
//TODO: port to libcosmic
|
||||
let menu_root = |label| {
|
||||
widget::button(widget::text(label))
|
||||
|
|
@ -116,6 +116,9 @@ pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message
|
|||
.style(theme::Button::MenuRoot)
|
||||
};
|
||||
|
||||
let menu_folder =
|
||||
|label| menu_button!(widget::text(label), horizontal_space(Length::Fill), ">");
|
||||
|
||||
let find_key = |action: &Action| -> String {
|
||||
for (key_bind, key_action) in key_binds.iter() {
|
||||
if action == key_action {
|
||||
|
|
@ -137,6 +140,12 @@ pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message
|
|||
)
|
||||
};
|
||||
|
||||
let mut profile_items = Vec::with_capacity(config.profiles.len());
|
||||
for (name, id) in config.profile_names() {
|
||||
profile_items.push(menu_item(name, Action::ProfileOpen(id)));
|
||||
}
|
||||
//TODO: what to do if there are no profiles?
|
||||
|
||||
MenuBar::new(vec![
|
||||
MenuTree::with_children(
|
||||
menu_root(fl!("file")),
|
||||
|
|
@ -144,6 +153,9 @@ pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message
|
|||
menu_item(fl!("new-tab"), Action::TabNew),
|
||||
menu_item(fl!("new-window"), Action::WindowNew),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
MenuTree::with_children(menu_folder(fl!("profile")), profile_items),
|
||||
menu_item(fl!("menu-profiles"), Action::Profiles),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item(fl!("close-tab"), Action::TabClose),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item(fl!("quit"), Action::WindowClose),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ use indexmap::IndexSet;
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
mem,
|
||||
io, mem,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc, Weak,
|
||||
|
|
@ -38,7 +38,10 @@ use tokio::sync::mpsc;
|
|||
|
||||
pub use alacritty_terminal::grid::Scroll as TerminalScroll;
|
||||
|
||||
use crate::{config::Config as AppConfig, mouse_reporter::MouseReporter};
|
||||
use crate::{
|
||||
config::{Config as AppConfig, ProfileId},
|
||||
mouse_reporter::MouseReporter,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Size {
|
||||
|
|
@ -187,21 +190,22 @@ impl Metadata {
|
|||
}
|
||||
|
||||
pub struct Terminal {
|
||||
default_attrs: Attrs<'static>,
|
||||
buffer: Arc<Buffer>,
|
||||
size: Size,
|
||||
pub term: Arc<FairMutex<Term<EventProxy>>>,
|
||||
colors: Colors,
|
||||
dim_font_weight: Weight,
|
||||
bold_font_weight: Weight,
|
||||
use_bright_bold: bool,
|
||||
notifier: Notifier,
|
||||
pub context_menu: Option<cosmic::iced::Point>,
|
||||
pub metadata_set: IndexSet<Metadata>,
|
||||
pub needs_update: bool,
|
||||
pub profile_id_opt: Option<ProfileId>,
|
||||
pub term: Arc<FairMutex<Term<EventProxy>>>,
|
||||
bold_font_weight: Weight,
|
||||
buffer: Arc<Buffer>,
|
||||
colors: Colors,
|
||||
default_attrs: Attrs<'static>,
|
||||
dim_font_weight: Weight,
|
||||
mouse_reporter: MouseReporter,
|
||||
notifier: Notifier,
|
||||
search_regex_opt: Option<RegexSearch>,
|
||||
search_value: String,
|
||||
pub metadata_set: IndexSet<Metadata>,
|
||||
mouse_reporter: MouseReporter,
|
||||
size: Size,
|
||||
use_bright_bold: bool,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
|
|
@ -214,7 +218,8 @@ impl Terminal {
|
|||
options: Options,
|
||||
app_config: &AppConfig,
|
||||
colors: Colors,
|
||||
) -> Self {
|
||||
profile_id_opt: Option<ProfileId>,
|
||||
) -> Result<Self, io::Error> {
|
||||
let font_stretch = app_config.typed_font_stretch();
|
||||
let font_weight = app_config.font_weight;
|
||||
let dim_font_weight = app_config.dim_font_weight;
|
||||
|
|
@ -267,29 +272,30 @@ impl Terminal {
|
|||
)));
|
||||
|
||||
let window_id = 0;
|
||||
let pty = tty::new(&options, size.into(), window_id).unwrap();
|
||||
let pty = tty::new(&options, size.into(), window_id)?;
|
||||
|
||||
let pty_event_loop = EventLoop::new(term.clone(), event_proxy, pty, options.hold, false);
|
||||
let notifier = Notifier(pty_event_loop.channel());
|
||||
let _pty_join_handle = pty_event_loop.spawn();
|
||||
|
||||
Self {
|
||||
colors,
|
||||
dim_font_weight: Weight(dim_font_weight),
|
||||
Ok(Self {
|
||||
bold_font_weight: Weight(bold_font_weight),
|
||||
use_bright_bold,
|
||||
default_attrs,
|
||||
buffer: Arc::new(buffer),
|
||||
size,
|
||||
term,
|
||||
notifier,
|
||||
colors,
|
||||
context_menu: None,
|
||||
needs_update: true,
|
||||
search_regex_opt: None,
|
||||
search_value: String::new(),
|
||||
default_attrs,
|
||||
dim_font_weight: Weight(dim_font_weight),
|
||||
metadata_set,
|
||||
mouse_reporter: Default::default(),
|
||||
}
|
||||
needs_update: true,
|
||||
notifier,
|
||||
profile_id_opt,
|
||||
search_regex_opt: None,
|
||||
search_value: String::new(),
|
||||
size,
|
||||
term,
|
||||
use_bright_bold,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn buffer_weak(&self) -> Weak<Buffer> {
|
||||
|
|
@ -550,7 +556,7 @@ impl Terminal {
|
|||
update_cell_size = true;
|
||||
}
|
||||
|
||||
if let Some(colors) = themes.get(config.syntax_theme()) {
|
||||
if let Some(colors) = themes.get(config.syntax_theme(self.profile_id_opt)) {
|
||||
let mut changed = false;
|
||||
for i in 0..color::COUNT {
|
||||
if self.colors[i] != colors[i] {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue