Compare commits

..

No commits in common. "local/libcosmic-path-patch" and "pre-1.7-merge-20260827" have entirely different histories.

17 changed files with 684 additions and 540 deletions

957
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.6.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"

18
debian/changelog vendored
View file

@ -1,21 +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
* Epoch 1.8.0 version update
-- Jeremy Soller <jeremy@system76.com> Wed, 02 Sep 2026 08:14:30 -0600
cosmic-edit (1.7.0) noble; urgency=medium
* Epoch 1.7.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 25 Aug 2026 09:19:59 -0600
cosmic-edit (1.6.0) noble; urgency=medium cosmic-edit (1.6.0) noble; urgency=medium
* Epoch 1.6.0 version update * Epoch 1.6.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

@ -1,5 +1,5 @@
save = Gem save = Gem
cancel = Afbryd cancel = Annuller
quit = Afslut quit = Afslut
cosmic-text-editor = COSMIC-teksteditor cosmic-text-editor = COSMIC-teksteditor
new-document = Nyt dokument new-document = Nyt dokument

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},
@ -194,7 +194,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub enum Action { pub enum Action {
Todo, Todo,
NoOp,
About, About,
CloseFile, CloseFile,
CloseProject(usize), CloseProject(usize),
@ -245,7 +244,6 @@ impl Action {
fn message(&self, entity_opt: Option<Entity>) -> Message { fn message(&self, entity_opt: Option<Entity>) -> Message {
match self { match self {
Self::Todo => Message::Todo, Self::Todo => Message::Todo,
Self::NoOp => Message::NoOp,
Self::About => Message::ToggleContextPage(ContextPage::About), Self::About => Message::ToggleContextPage(ContextPage::About),
Self::CloseFile => Message::CloseFile, Self::CloseFile => Message::CloseFile,
Self::CloseProject(project_i) => Message::CloseProject(*project_i), Self::CloseProject(project_i) => Message::CloseProject(*project_i),
@ -373,7 +371,6 @@ pub enum Message {
Modifiers(Modifiers), Modifiers(Modifiers),
NewFile, NewFile,
NewWindow, NewWindow,
NoOp,
NotifyEvent(notify::Event), NotifyEvent(notify::Event),
NotifyWatcher(WatcherWrapper), NotifyWatcher(WatcherWrapper),
OpenFile(PathBuf), OpenFile(PathBuf),
@ -403,7 +400,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 +409,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),
@ -2165,9 +2162,6 @@ impl Application for App {
} }
} }
} }
Message::NoOp => {
// Do nothing
}
Message::NotifyEvent(event) => { Message::NotifyEvent(event) => {
// Reload tabs that changed // Reload tabs that changed
let mut tab_reload = Vec::new(); let mut tab_reload = Vec::new();
@ -2713,7 +2707,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();
@ -2792,9 +2788,7 @@ impl Application for App {
} }
Message::TabCloseForce(entity) => { Message::TabCloseForce(entity) => {
// Activate closest item // Activate closest item
if self.tab_model.active() == entity if let Some(position) = self.tab_model.position(entity) {
&& let Some(position) = self.tab_model.position(entity)
{
if position > 0 { if position > 0 {
self.tab_model.activate_position(position - 1); self.tab_model.activate_position(position - 1);
} else { } else {
@ -2821,15 +2815,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 => {
@ -3034,19 +3028,12 @@ impl Application for App {
} }
fn header_start(&self) -> Vec<Element<'_, Message>> { fn header_start(&self) -> Vec<Element<'_, Message>> {
let has_selection = match self.active_tab() {
Some(Tab::Editor(tab)) => tab.editor.lock().unwrap().selection() != Selection::None,
Some(Tab::GitDiff(_)) => false,
None => false,
};
vec![menu_bar( vec![menu_bar(
&self.core, &self.core,
&self.config, &self.config,
&self.config_state, &self.config_state,
&self.key_binds, &self.key_binds,
&self.projects, &self.projects,
has_selection,
)] )]
} }
@ -3091,25 +3078,23 @@ 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 has_selection = tab.editor.lock().unwrap().selection() != Selection::None; let mut popover = widget::popover(text_box);
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))
) .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,68 @@ 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, ) -> Element<'a, Message> {
) -> Vec<widget::menu::Tree<Message>> { fn key_style(theme: &cosmic::Theme) -> TextStyle {
let item = // TODO use wayland popups
|label: String, action: Action| MenuItem::Button(label, None, TabAction(entity, action)); let mut color = theme.cosmic().background(false).component.on;
let optional = |label: String, action: Action, enabled: bool| { color.alpha *= 0.75;
if enabled { TextStyle {
MenuItem::Button(label, None, TabAction(entity, action)) color: Some(color.into()),
} else { ..Default::default()
MenuItem::ButtonDisabled(label, None, TabAction(entity, action))
} }
}
let 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(),
])
.on_press(Message::TabContextAction(entity, menu_action))
}; };
let key_binds: HashMap<KeyBind, TabAction> = key_binds widget::container(
.iter() cosmic::widget::menu::menu_column::MenuColumn::with_children([
.map(|(key_bind, action)| (key_bind.clone(), TabAction(entity, *action))) menu_item(fl!("undo"), Action::Undo).into(),
.collect(); menu_item(fl!("redo"), Action::Redo).into(),
widget::menu::items( divider::horizontal::light().into(),
&key_binds, menu_item(fl!("cut"), Action::Cut).into(),
vec![ menu_item(fl!("copy"), Action::Copy).into(),
item(fl!("undo"), Action::Undo), menu_item(fl!("paste"), Action::Paste).into(),
item(fl!("redo"), Action::Redo), menu_item(fl!("select-all"), Action::SelectAll).into(),
MenuItem::Divider, ]),
optional(fl!("cut"), Action::Cut, has_selection),
optional(fl!("copy"), Action::Copy, has_selection),
item(fl!("paste"), Action::Paste),
item(fl!("select-all"), Action::SelectAll),
],
) )
.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>(
@ -164,7 +186,6 @@ pub fn menu_bar<'a>(
config_state: &ConfigState, config_state: &ConfigState,
key_binds: &HashMap<KeyBind, Action>, key_binds: &HashMap<KeyBind, Action>,
projects: &Vec<(String, PathBuf)>, projects: &Vec<(String, PathBuf)>,
has_selection: bool,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
//TODO: port to libcosmic //TODO: port to libcosmic
let menu_tab_width = |tab_width: u16| { let menu_tab_width = |tab_width: u16| {
@ -257,16 +278,8 @@ pub fn menu_bar<'a>(
MenuItem::Button(fl!("undo"), None, Action::Undo), MenuItem::Button(fl!("undo"), None, Action::Undo),
MenuItem::Button(fl!("redo"), None, Action::Redo), MenuItem::Button(fl!("redo"), None, Action::Redo),
MenuItem::Divider, MenuItem::Divider,
if has_selection { MenuItem::Button(fl!("cut"), None, Action::Cut),
MenuItem::Button(fl!("cut"), None, Action::Cut) MenuItem::Button(fl!("copy"), None, Action::Copy),
} else {
MenuItem::ButtonDisabled(fl!("cut"), None, Action::NoOp)
},
if has_selection {
MenuItem::Button(fl!("copy"), None, Action::Copy)
} else {
MenuItem::ButtonDisabled(fl!("copy"), None, Action::NoOp)
},
MenuItem::Button(fl!("paste"), None, Action::Paste), MenuItem::Button(fl!("paste"), None, Action::Paste),
MenuItem::Button(fl!("select-all"), None, Action::SelectAll), MenuItem::Button(fl!("select-all"), None, Action::SelectAll),
MenuItem::Divider, MenuItem::Divider,

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();