Compare commits

..

No commits in common. "936e57b63eeeee0ac16b3a4e6ae6e78179e352b5" and "85da7f90f2a562ea8c8d526b61463f034167bc0d" have entirely different histories.

16 changed files with 426 additions and 388 deletions

581
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "cosmic-edit" name = "cosmic-edit"
version = "1.9.0" version = "1.8.0"
authors = ["Jeremy Soller <jeremy@system76.com>"] authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2024" edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"

6
debian/changelog vendored
View file

@ -1,9 +1,3 @@
cosmic-edit (1.9.0) noble; urgency=medium
* Epoch 1.9.0 version update
-- Michael Murphy <michael@mmurphy.dev> Mon, 14 Sep 2026 21:54:06 +0200
cosmic-edit (1.8.0) noble; urgency=medium cosmic-edit (1.8.0) noble; urgency=medium
* Epoch 1.8.0 version update * Epoch 1.8.0 version update

5
debian/rules vendored
View file

@ -1,12 +1,15 @@
#!/usr/bin/make -f #!/usr/bin/make -f
export DESTDIR = debian/cosmic-edit export DESTDIR = debian/cosmic-edit
export VENDOR ?= 1
%: %:
dh $@ dh $@
override_dh_auto_clean: override_dh_auto_clean:
ischroot || just vendor if ! ischroot && test "${VENDOR}" = "1"; then \
just vendor; \
fi
override_dh_auto_build: override_dh_auto_build:
just build-vendored just build-vendored

View file

@ -1,6 +1,4 @@
tar-ignore=.github tar-ignore=.github
tar-ignore=target/.rustc_info.json tar-ignore=.vscode
tar-ignore=target/CACHEDIR.TAG
tar-ignore=target/debug
tar-ignore=target/release
tar-ignore=vendor tar-ignore=vendor
tar-ignore=target

View file

@ -60,7 +60,7 @@ default-zoom-step = Nagyítási mérték
### Keyboard shortcuts ### Keyboard shortcuts
keyboard-shortcuts = Gyorsbillentyűk keyboard-shortcuts = Gyorsbillentyűk
enable-vim-bindings = Vim-billentyűkombinációk engedélyezése enable-vim-bindings = Vim billentyűkombinációk engedélyezése
# Find # Find
find-placeholder = Keresés… find-placeholder = Keresés…
find-previous = Előző találat find-previous = Előző találat

View file

@ -1,9 +1,9 @@
cancel = Semmet cancel = Sefsex
undo = Ssemmet undo = Ssemmet
save = Sekles save = Sekles
quit = Ffeɣ quit = Tuffɣa
replace = Semselsi replace = Semselsi
support = Tallelt support = Tallalt
settings = Iɣewwaṛen settings = Iɣewwaṛen
appearance = Timeẓri appearance = Timeẓri
theme = Asentel theme = Asentel
@ -16,7 +16,7 @@ default-size = Tiddi tamezwert
zoom-out = Asemẓi zoom-out = Asemẓi
view = Wali view = Wali
menu-settings = Iɣewwaṛen… menu-settings = Iɣewwaṛen…
match-desktop = Semṣada akked tnarit match-desktop = Amṣada d tnarit
file = Afaylu file = Afaylu
new-window = Asfaylu amaynut new-window = Asfaylu amaynut
edit = Ẓreg edit = Ẓreg

View file

@ -12,7 +12,7 @@ support = Piştgirî
quit = Biqedîne quit = Biqedîne
find-placeholder = Bibîne... find-placeholder = Bibîne...
copy = Jê bigire copy = Jê bigire
paste = Pê ve bike paste = Pêve bike
match-desktop = Bi sermaseyê re li hev bîne match-desktop = Bi sermaseyê re li hev bîne
select-all = Hemûyan hilbijêre select-all = Hemûyan hilbijêre
zoom-in = Nêzîk bike zoom-in = Nêzîk bike

View file

@ -1,13 +1,8 @@
settings = Configuracion settings = Paramètres
appearance = Aparéncia appearance = Aparéncia
theme = Tèma theme = Tèma
match-desktop = Assortir al burèu match-desktop = Assortir al burèu
dark = Escur dark = Fosc
light = Clar light = Clar
file = Fichièr file = Fichièr
close-file = Tampar lo fichièr close-file = Tampar lo fichièr
cancel = Anullar
repository = Depaus
menu-settings = Configuracion...
quit = Quitar
replace = Remplaçar

View file

@ -1,5 +1,5 @@
cosmic-text-editor = Edytor Tekstu COSMIC cosmic-text-editor = Edytor Tekstu COSMIC
comment = Edytor tekstu dla środowiska COSMIC comment = Edytor tekstu pulpitu COSMIC
new-document = Nowy dokument new-document = Nowy dokument
open-project = Otwórz projekt open-project = Otwórz projekt
todo = TODO todo = TODO
@ -64,7 +64,7 @@ default-zoom-step = Stopniowanie powiększania
keyboard-shortcuts = Skróty klawiszowe keyboard-shortcuts = Skróty klawiszowe
enable-vim-bindings = Włącz tryb Vim enable-vim-bindings = Włącz tryb Vim
# Find # Find
find-placeholder = Wyszukaj… find-placeholder = Szukaj…
find-previous = Znajdź poprzedni find-previous = Znajdź poprzedni
find-next = Znajdź następny find-next = Znajdź następny
replace-placeholder = Zastąp… replace-placeholder = Zastąp…
@ -106,8 +106,8 @@ cut = Wytnij
copy = Kopiuj copy = Kopiuj
paste = Wklej paste = Wklej
select-all = Zaznacz wszystko select-all = Zaznacz wszystko
find = Wyszukaj find = Szukaj
find-in-project = Wyszukaj w projekcie… find-in-project = Szukaj w projekcie…
spell-check = Sprawdź ortografię… spell-check = Sprawdź ortografię…
## View ## View

View file

@ -1,6 +0,0 @@
cancel = ยกเลิก
settings = การตั้งค่า
dark = มืด
light = สว่าง
replace = แทนที่
appearance = ลักษณะ

View file

@ -11,7 +11,7 @@ use cosmic::{
cosmic_theme, executor, cosmic_theme, executor,
font::Font, font::Font,
iced::{ iced::{
self, Alignment, Background, Color, Length, Limits, Subscription, self, Alignment, Background, Color, Length, Limits, Point, Subscription,
advanced::graphics::text::font_system, advanced::graphics::text::font_system,
clipboard, event, clipboard, event,
futures::{self, SinkExt}, futures::{self, SinkExt},
@ -403,7 +403,7 @@ pub enum Message {
SaveAsResult(segmented_button::Entity, DialogResult), SaveAsResult(segmented_button::Entity, DialogResult),
Scroll(f32), Scroll(f32),
SelectAll, SelectAll,
Surface(surface::Action<Message>), Surface(surface::Action),
SystemThemeModeChange(cosmic_theme::ThemeMode), SystemThemeModeChange(cosmic_theme::ThemeMode),
SyntaxTheme(usize, bool), SyntaxTheme(usize, bool),
TabActivate(segmented_button::Entity), TabActivate(segmented_button::Entity),
@ -412,7 +412,7 @@ pub enum Message {
TabClose(segmented_button::Entity), TabClose(segmented_button::Entity),
TabCloseForce(segmented_button::Entity), TabCloseForce(segmented_button::Entity),
TabContextAction(segmented_button::Entity, Action), TabContextAction(segmented_button::Entity, Action),
TabContextMenu(segmented_button::Entity, bool), TabContextMenu(segmented_button::Entity, Option<Point>),
TabNext, TabNext,
TabPrev, TabPrev,
TabSetCursor(segmented_button::Entity, Cursor), TabSetCursor(segmented_button::Entity, Cursor),
@ -2713,7 +2713,9 @@ impl Application for App {
} }
} }
Message::Surface(a) => { Message::Surface(a) => {
return cosmic::task::message(cosmic::Action::Surface(a)); return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(a),
));
} }
Message::SystemThemeModeChange(_theme_mode) => { Message::SystemThemeModeChange(_theme_mode) => {
return self.update_config(); return self.update_config();
@ -2821,15 +2823,15 @@ impl Application for App {
Message::TabContextAction(entity, action) => { Message::TabContextAction(entity, action) => {
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) { if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
// Close context menu // Close context menu
tab.context_menu_open = false; tab.context_menu = None;
// Run action's message // Run action's message
return self.update(action.message(None)); return self.update(action.message(None));
} }
} }
Message::TabContextMenu(entity, open) => { Message::TabContextMenu(entity, position_opt) => {
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) { if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
// Update context menu // Update context menu
tab.context_menu_open = open; tab.context_menu = position_opt;
} }
} }
Message::TabNext => { Message::TabNext => {
@ -3091,25 +3093,24 @@ impl Application for App {
.on_focus(Message::FindFocused(false)) .on_focus(Message::FindFocused(false))
.on_auto_scroll(Message::AutoScroll) .on_auto_scroll(Message::AutoScroll)
.on_changed(Message::TabChanged(tab_id)) .on_changed(Message::TabChanged(tab_id))
.has_context_menu(tab.context_menu_open) .has_context_menu(tab.context_menu.is_some())
.on_context_menu(move |open| Message::TabContextMenu(tab_id, open)); .on_context_menu(move |position_opt| {
Message::TabContextMenu(tab_id, position_opt)
});
if self.config.highlight_current_line { if self.config.highlight_current_line {
text_box = text_box.highlight_current_line(); text_box = text_box.highlight_current_line();
} }
if self.config.line_numbers { if self.config.line_numbers {
text_box = text_box.line_numbers(); text_box = text_box.line_numbers();
} }
let mut popover = widget::popover(text_box);
let has_selection = tab.editor.lock().unwrap().selection() != Selection::None; let has_selection = tab.editor.lock().unwrap().selection() != Selection::None;
let mut context_menu = widget::context_menu( if let Some(point) = tab.context_menu {
text_box, popover = popover
Some(menu::context_menu(&self.key_binds, tab_id, has_selection)), .popup(menu::context_menu(&self.key_binds, tab_id, has_selection))
) .position(widget::popover::Position::Point(point));
.on_close(Message::TabContextMenu(tab_id, false))
.on_surface_action(Message::Surface);
if let Some(window_id) = self.core().main_window_id() {
context_menu = context_menu.window_id(window_id);
} }
tab_column = tab_column.push(context_menu); tab_column = tab_column.push(popover);
if self.config.vim_bindings { if self.config.vim_bindings {
let status = { let status = {
let editor = tab.editor.lock().unwrap(); let editor = tab.editor.lock().unwrap();

View file

@ -1,15 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::widget::menu::Item as MenuItem; use cosmic::widget::menu::Item as MenuItem;
use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::{ use cosmic::{
Element, Element,
app::Core, app::Core,
iced::{
Background, Border, Length, advanced::widget::text::Style as TextStyle, widget::column,
},
theme,
widget::{ widget::{
self, self, divider,
menu::{ItemHeight, ItemWidth}, menu::{ItemHeight, ItemWidth, menu_button},
responsive_menu_bar, segmented_button, responsive_menu_bar, segmented_button, space,
}, },
}; };
use std::{collections::HashMap, path::PathBuf, sync::LazyLock}; use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
@ -113,49 +116,81 @@ fn format_recent_menu_path(path: &PathBuf, home_dir_opt: Option<&PathBuf>) -> St
truncate_middle(&display, RECENT_MENU_LABEL_MAX_CHARS) truncate_middle(&display, RECENT_MENU_LABEL_MAX_CHARS)
} }
/// A context menu action dispatched to the tab the menu was opened on. pub fn context_menu<'a>(
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TabAction(pub segmented_button::Entity, pub Action);
impl MenuAction for TabAction {
type Message = Message;
fn message(&self) -> Message {
Message::TabContextAction(self.0, self.1)
}
}
pub fn context_menu(
key_binds: &HashMap<KeyBind, Action>, key_binds: &HashMap<KeyBind, Action>,
entity: segmented_button::Entity, entity: segmented_button::Entity,
has_selection: bool, has_selection: bool,
) -> Vec<widget::menu::Tree<Message>> { ) -> Element<'a, Message> {
let item = fn key_style(theme: &cosmic::Theme) -> TextStyle {
|label: String, action: Action| MenuItem::Button(label, None, TabAction(entity, action)); // TODO use wayland popups
let optional = |label: String, action: Action, enabled: bool| { let mut color = theme.cosmic().background(false).component.on;
if enabled { color.alpha *= 0.75;
MenuItem::Button(label, None, TabAction(entity, action)) TextStyle {
} else { color: Some(color.into()),
MenuItem::ButtonDisabled(label, None, TabAction(entity, action)) ..Default::default()
} }
}
let base_menu_item = |menu_label, menu_action| {
let mut key = String::new();
for (key_bind, key_action) in key_binds.iter() {
if key_action == &menu_action {
key = key_bind.to_string();
break;
}
}
menu_button(vec![
widget::text(menu_label).into(),
space::horizontal().into(),
widget::text(key)
.class(theme::Text::Custom(key_style))
.into(),
])
}; };
let key_binds: HashMap<KeyBind, TabAction> = key_binds let optional_menu_item = |menu_label, menu_action, disabled| {
.iter() base_menu_item(menu_label, menu_action).on_press_maybe(if disabled {
.map(|(key_bind, action)| (key_bind.clone(), TabAction(entity, *action))) None
.collect(); } else {
widget::menu::items( Some(Message::TabContextAction(entity, menu_action))
&key_binds, })
vec![ };
item(fl!("undo"), Action::Undo),
item(fl!("redo"), Action::Redo), let menu_item = |menu_label, menu_action| {
MenuItem::Divider, base_menu_item(menu_label, menu_action)
optional(fl!("cut"), Action::Cut, has_selection), .on_press(Message::TabContextAction(entity, menu_action))
optional(fl!("copy"), Action::Copy, has_selection), };
item(fl!("paste"), Action::Paste),
item(fl!("select-all"), Action::SelectAll), widget::container(
], cosmic::widget::menu::menu_column::MenuColumn::with_children([
menu_item(fl!("undo"), Action::Undo).into(),
menu_item(fl!("redo"), Action::Redo).into(),
divider::horizontal::light().into(),
optional_menu_item(fl!("cut"), Action::Cut, !has_selection).into(),
optional_menu_item(fl!("copy"), Action::Copy, !has_selection).into(),
menu_item(fl!("paste"), Action::Paste).into(),
menu_item(fl!("select-all"), Action::SelectAll).into(),
]),
) )
.padding(1)
//TODO: move style to libcosmic
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background(false).component;
widget::container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(240.0))
.into()
} }
pub fn menu_bar<'a>( pub fn menu_bar<'a>(

View file

@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::{iced::advanced::graphics::text::font_system, widget::icon}; use cosmic::{
iced::{Point, advanced::graphics::text::font_system},
widget::icon,
};
use cosmic_files::mime_icon::{FALLBACK_MIME_ICON, mime_for_path, mime_icon}; use cosmic_files::mime_icon::{FALLBACK_MIME_ICON, mime_for_path, mime_icon};
use cosmic_text::{Attrs, Buffer, Cursor, Edit, Selection, Shaping, SyntaxEditor, ViEditor, Wrap}; use cosmic_text::{Attrs, Buffer, Cursor, Edit, Selection, Shaping, SyntaxEditor, ViEditor, Wrap};
use regex::Regex; use regex::Regex;
@ -48,7 +51,7 @@ pub struct EditorTab {
pub path_opt: Option<PathBuf>, pub path_opt: Option<PathBuf>,
attrs: Attrs<'static>, attrs: Attrs<'static>,
pub editor: Mutex<ViEditor<'static, 'static>>, pub editor: Mutex<ViEditor<'static, 'static>>,
pub context_menu_open: bool, pub context_menu: Option<Point>,
pub zoom_adj: i8, pub zoom_adj: i8,
} }
@ -72,7 +75,7 @@ impl EditorTab {
path_opt: None, path_opt: None,
attrs, attrs,
editor: Mutex::new(ViEditor::new(editor)), editor: Mutex::new(ViEditor::new(editor)),
context_menu_open: false, context_menu: None,
zoom_adj, zoom_adj,
}; };

View file

@ -51,7 +51,7 @@ pub struct TextBox<'a, Message> {
on_focus: Option<Message>, on_focus: Option<Message>,
click_timing: Duration, click_timing: Duration,
has_context_menu: bool, has_context_menu: bool,
on_context_menu: Option<Box<dyn Fn(bool) -> Message + 'a>>, on_context_menu: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
highlight_current_line: bool, highlight_current_line: bool,
line_numbers: bool, line_numbers: bool,
} }
@ -107,7 +107,10 @@ where
self self
} }
pub fn on_context_menu(mut self, on_context_menu: impl Fn(bool) -> Message + 'a) -> Self { pub fn on_context_menu(
mut self,
on_context_menu: impl Fn(Option<Point>) -> Message + 'a,
) -> Self {
self.on_context_menu = Some(Box::new(on_context_menu)); self.on_context_menu = Some(Box::new(on_context_menu));
self self
} }
@ -1264,9 +1267,14 @@ where
// Update context menu state // Update context menu state
if let Some(on_context_menu) = &self.on_context_menu { if let Some(on_context_menu) = &self.on_context_menu {
shell.publish((on_context_menu)( shell.publish((on_context_menu)(if self.has_context_menu {
!self.has_context_menu && matches!(button, Button::Right), None
)); } else {
match button {
Button::Right => Some(p),
_ => None,
}
}));
} }
shell.capture_event(); shell.capture_event();