Compare commits
No commits in common. "local/libcosmic-path-patch" and "pre-1.9-merge-20260924" have entirely different histories.
local/libc
...
pre-1.9-me
25 changed files with 721 additions and 783 deletions
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,10 +1,8 @@
|
|||
Replace this text with a description of your changes, and why these changes are important. Read and acknowledge the list below. PRs without a completed checkbox will be closed.
|
||||
- [ ] I have disclosed use of any AI generated code in my commit messages.
|
||||
- If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR.
|
||||
- In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment.
|
||||
- [ ] I understand these changes in full and will be able to respond to review comments.
|
||||
- [ ] My change is accurately described in the commit message.
|
||||
- [ ] My contribution is tested and working as described.
|
||||
- [ ] I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.
|
||||
|
||||
---
|
||||
|
||||
- I have not included any LLM (also known as AI) generated content in this PR, including code, comments, and descriptions.
|
||||
- I understand these changes in full and will be able to respond to review comments.
|
||||
- My change is accurately described in the commit message.
|
||||
- My contribution is tested and working as described.
|
||||
- I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.
|
||||
- [ ] I have read and followed all of the above requirements. For more information, see our [contributor guide](https://github.com/pop-os/pop/blob/master/CONTRIBUTING.md).
|
||||
|
|
|
|||
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
|
|
@ -1,47 +0,0 @@
|
|||
name: Continuous integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: install toolchain
|
||||
run: rustup toolchain install nightly --component rustfmt
|
||||
- name: fmt
|
||||
run: cargo +nightly fmt --all --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: install system dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y pkg-config libglib2.0-dev libxkbcommon-dev libwayland-dev
|
||||
- name: install toolchain
|
||||
run: rustup show
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: clippy
|
||||
run: cargo clippy --all-features --all-targets -- -D warnings
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: install system dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y pkg-config libglib2.0-dev libxkbcommon-dev libwayland-dev
|
||||
- name: install toolchain
|
||||
run: rustup show
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test
|
||||
run: cargo test --all-features
|
||||
14
.github/workflows/require-checklist.yml
vendored
14
.github/workflows/require-checklist.yml
vendored
|
|
@ -1,14 +0,0 @@
|
|||
# From https://github.com/mheap/require-checklist-action, license MIT
|
||||
name: Require Checklist
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mheap/require-checklist-action@v2
|
||||
with:
|
||||
requireChecklist: true
|
||||
763
Cargo.lock
generated
763
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cosmic-edit"
|
||||
version = "1.10.0"
|
||||
version = "1.8.0"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
|
|
|
|||
70
cargo.just
70
cargo.just
|
|
@ -1,70 +0,0 @@
|
|||
cargo-target-dir := env('CARGO_TARGET_DIR', 'target')
|
||||
|
||||
# Compile with debug profile
|
||||
[no-cd]
|
||||
build-debug *args:
|
||||
cargo build {{args}}
|
||||
|
||||
# Compile with release profile
|
||||
[no-cd]
|
||||
build-release *args: (build-debug '--release' args)
|
||||
|
||||
# Compile with a vendored tarball
|
||||
[no-cd]
|
||||
build-vendored *args: vendor-extract (build-release '--frozen --offline' args)
|
||||
|
||||
# Check for errors and linter warnings
|
||||
[no-cd]
|
||||
check *args:
|
||||
cargo clippy --all-features {{args}} -- -W clippy::pedantic
|
||||
|
||||
# Runs a check with JSON message format for IDE integration
|
||||
[no-cd]
|
||||
check-json: (check '--message-format=json')
|
||||
|
||||
# Remove Cargo build artifacts
|
||||
[no-cd]
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
# Also remove .cargo and vendored dependencies
|
||||
[no-cd]
|
||||
clean-dist: clean
|
||||
rm -rf .cargo vendor vendor.tar target
|
||||
|
||||
# Run the application for testing purposes
|
||||
[no-cd]
|
||||
run *args:
|
||||
cargo run {{args}}
|
||||
|
||||
# Run `cargo test`
|
||||
[no-cd]
|
||||
test *args:
|
||||
cargo test {{args}}
|
||||
|
||||
# Vendor Cargo dependencies locally
|
||||
[no-cd]
|
||||
vendor:
|
||||
#!/usr/bin/env bash
|
||||
mkdir -p .cargo
|
||||
cargo vendor --locked | head -n -1 > .cargo/config.toml
|
||||
echo 'directory = "vendor"' >> .cargo/config.toml
|
||||
echo >> .cargo/config.toml
|
||||
echo '[env]' >> .cargo/config.toml
|
||||
if [ -n "${SOURCE_DATE_EPOCH}" ]
|
||||
then
|
||||
source_date="$(date -d "@${SOURCE_DATE_EPOCH}" "+%Y-%m-%d")"
|
||||
echo "VERGEN_GIT_COMMIT_DATE = \"${source_date}\"" >> .cargo/config.toml
|
||||
fi
|
||||
if [ -n "${SOURCE_GIT_HASH}" ]
|
||||
then
|
||||
echo "VERGEN_GIT_SHA = \"${SOURCE_GIT_HASH}\"" >> .cargo/config.toml
|
||||
fi
|
||||
tar pcf vendor.tar .cargo vendor
|
||||
rm -rf .cargo vendor
|
||||
|
||||
# Extracts vendored dependencies
|
||||
[no-cd]
|
||||
vendor-extract:
|
||||
rm -rf vendor
|
||||
tar pxf vendor.tar
|
||||
12
debian/changelog
vendored
12
debian/changelog
vendored
|
|
@ -1,15 +1,3 @@
|
|||
cosmic-edit (1.10.0) noble; urgency=medium
|
||||
|
||||
* Epoch 1.10.0 version update
|
||||
|
||||
-- Michael Murphy <michael@mmurphy.dev> Mon, 05 Oct 2026 21:19:33 +0200
|
||||
|
||||
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
|
||||
|
|
|
|||
5
debian/rules
vendored
5
debian/rules
vendored
|
|
@ -1,12 +1,15 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
export DESTDIR = debian/cosmic-edit
|
||||
export VENDOR ?= 1
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_clean:
|
||||
ischroot || just vendor
|
||||
if ! ischroot && test "${VENDOR}" = "1"; then \
|
||||
just vendor; \
|
||||
fi
|
||||
|
||||
override_dh_auto_build:
|
||||
just build-vendored
|
||||
|
|
|
|||
6
debian/source/options
vendored
6
debian/source/options
vendored
|
|
@ -1,6 +1,4 @@
|
|||
tar-ignore=.github
|
||||
tar-ignore=target/.rustc_info.json
|
||||
tar-ignore=target/CACHEDIR.TAG
|
||||
tar-ignore=target/debug
|
||||
tar-ignore=target/release
|
||||
tar-ignore=.vscode
|
||||
tar-ignore=vendor
|
||||
tar-ignore=target
|
||||
|
|
@ -53,14 +53,14 @@ dark = Sötét
|
|||
light = Világos
|
||||
syntax-dark = Sötét szintaxis
|
||||
syntax-light = Világos szintaxis
|
||||
default-font = Alapértelmezett betűkészlet
|
||||
default-font = Alapértelmezett betűtípus
|
||||
default-font-size = Alapértelmezett betűméret
|
||||
default-zoom-step = Nagyítási mérték
|
||||
|
||||
### Keyboard shortcuts
|
||||
|
||||
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-placeholder = Keresés…
|
||||
find-previous = Előző találat
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
cancel = Semmet
|
||||
cancel = Sefsex
|
||||
undo = Ssemmet
|
||||
save = Sekles
|
||||
quit = Ffeɣ
|
||||
quit = Tuffɣa
|
||||
replace = Semselsi
|
||||
support = Tallelt
|
||||
support = Tallalt
|
||||
settings = Iɣewwaṛen
|
||||
appearance = Timeẓri
|
||||
theme = Asentel
|
||||
|
|
@ -16,7 +16,7 @@ default-size = Tiddi tamezwert
|
|||
zoom-out = Asemẓi
|
||||
view = Wali
|
||||
menu-settings = Iɣewwaṛen…
|
||||
match-desktop = Semṣada akked tnarit
|
||||
match-desktop = Amṣada d tnarit
|
||||
file = Afaylu
|
||||
new-window = Asfaylu amaynut
|
||||
edit = Ẓreg
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ support = Piştgirî
|
|||
quit = Biqedîne
|
||||
find-placeholder = Bibîne...
|
||||
copy = Jê bigire
|
||||
paste = Pê ve bike
|
||||
paste = Pêve bike
|
||||
match-desktop = Bi sermaseyê re li hev bîne
|
||||
select-all = Hemûyan hilbijêre
|
||||
zoom-in = Nêzîk bike
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
settings = Configuracion
|
||||
settings = Paramètres
|
||||
appearance = Aparéncia
|
||||
theme = Tèma
|
||||
match-desktop = Assortir al burèu
|
||||
dark = Escur
|
||||
dark = Fosc
|
||||
light = Clar
|
||||
file = Fichièr
|
||||
close-file = Tampar lo fichièr
|
||||
cancel = Anullar
|
||||
repository = Depaus
|
||||
menu-settings = Configuracion...
|
||||
quit = Quitar
|
||||
replace = Remplaçar
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
cosmic-text-editor = Edytor Tekstu COSMIC
|
||||
comment = Edytor tekstu dla środowiska COSMIC
|
||||
comment = Edytor tekstu pulpitu COSMIC
|
||||
new-document = Nowy dokument
|
||||
open-project = Otwórz projekt
|
||||
todo = TODO
|
||||
|
|
@ -64,7 +64,7 @@ default-zoom-step = Stopniowanie powiększania
|
|||
keyboard-shortcuts = Skróty klawiszowe
|
||||
enable-vim-bindings = Włącz tryb Vim
|
||||
# Find
|
||||
find-placeholder = Wyszukaj…
|
||||
find-placeholder = Szukaj…
|
||||
find-previous = Znajdź poprzedni
|
||||
find-next = Znajdź następny
|
||||
replace-placeholder = Zastąp…
|
||||
|
|
@ -106,8 +106,8 @@ cut = Wytnij
|
|||
copy = Kopiuj
|
||||
paste = Wklej
|
||||
select-all = Zaznacz wszystko
|
||||
find = Wyszukaj
|
||||
find-in-project = Wyszukaj w projekcie…
|
||||
find = Szukaj
|
||||
find-in-project = Szukaj w projekcie…
|
||||
spell-check = Sprawdź ortografię…
|
||||
|
||||
## View
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
cancel = ยกเลิก
|
||||
settings = การตั้งค่า
|
||||
dark = มืด
|
||||
light = สว่าง
|
||||
replace = แทนที่
|
||||
appearance = ลักษณะ
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[toolchain]
|
||||
channel = "1.93"
|
||||
components = ["clippy", "rustfmt"]
|
||||
|
|
@ -112,8 +112,17 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize, Default)]
|
||||
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct ConfigState {
|
||||
pub recent_files: VecDeque<PathBuf>,
|
||||
pub recent_projects: VecDeque<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for ConfigState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
recent_files: VecDeque::new(),
|
||||
recent_projects: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ impl GitRepository {
|
|||
msg.push_str("\nstderr> ");
|
||||
msg.push_str(line);
|
||||
}
|
||||
Err(io::Error::other(msg))
|
||||
Err(io::Error::new(io::ErrorKind::Other, msg))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
286
src/main.rs
286
src/main.rs
|
|
@ -11,7 +11,7 @@ use cosmic::{
|
|||
cosmic_theme, executor,
|
||||
font::Font,
|
||||
iced::{
|
||||
self, Alignment, Background, Color, Length, Limits, Subscription,
|
||||
self, Alignment, Background, Color, Length, Limits, Point, Subscription,
|
||||
advanced::graphics::text::font_system,
|
||||
clipboard, event,
|
||||
futures::{self, SinkExt},
|
||||
|
|
@ -328,7 +328,6 @@ impl PartialEq for WatcherWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum NewTab {
|
||||
Tab(EditorTab),
|
||||
Exists(Entity),
|
||||
|
|
@ -404,7 +403,7 @@ pub enum Message {
|
|||
SaveAsResult(segmented_button::Entity, DialogResult),
|
||||
Scroll(f32),
|
||||
SelectAll,
|
||||
Surface(surface::Action<Message>),
|
||||
Surface(surface::Action),
|
||||
SystemThemeModeChange(cosmic_theme::ThemeMode),
|
||||
SyntaxTheme(usize, bool),
|
||||
TabActivate(segmented_button::Entity),
|
||||
|
|
@ -413,7 +412,7 @@ pub enum Message {
|
|||
TabClose(segmented_button::Entity),
|
||||
TabCloseForce(segmented_button::Entity),
|
||||
TabContextAction(segmented_button::Entity, Action),
|
||||
TabContextMenu(segmented_button::Entity, bool),
|
||||
TabContextMenu(segmented_button::Entity, Option<Point>),
|
||||
TabNext,
|
||||
TabPrev,
|
||||
TabSetCursor(segmented_button::Entity, Cursor),
|
||||
|
|
@ -530,7 +529,7 @@ impl App {
|
|||
continue;
|
||||
}
|
||||
let entry_path = entry.path();
|
||||
let node = match ProjectNode::new(entry_path) {
|
||||
let node = match ProjectNode::new(&entry_path) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
|
|
@ -683,11 +682,11 @@ impl App {
|
|||
//TODO: allow files to be open multiple times
|
||||
let mut activate_opt = None;
|
||||
for entity in self.tab_model.iter() {
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity)
|
||||
&& tab.path_opt.as_ref() == Some(&canonical)
|
||||
{
|
||||
activate_opt = Some(entity);
|
||||
break;
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity) {
|
||||
if tab.path_opt.as_ref() == Some(&canonical) {
|
||||
activate_opt = Some(entity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(entity) = activate_opt {
|
||||
|
|
@ -735,10 +734,10 @@ impl App {
|
|||
}
|
||||
let entities: Vec<_> = self.tab_model.iter().collect();
|
||||
for entity in entities {
|
||||
if self.tab_model.is_active(entity)
|
||||
&& let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity)
|
||||
{
|
||||
tab.set_config(&self.config);
|
||||
if self.tab_model.is_active(entity) {
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
tab.set_config(&self.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -755,10 +754,10 @@ impl App {
|
|||
}
|
||||
|
||||
fn save_config_state(&mut self) {
|
||||
if let Some(ref config_state_handler) = self.config_state_handler
|
||||
&& let Err(err) = self.config_state.write_entry(config_state_handler)
|
||||
{
|
||||
log::error!("failed to save config_state: {}", err);
|
||||
if let Some(ref config_state_handler) = self.config_state_handler {
|
||||
if let Err(err) = self.config_state.write_entry(config_state_handler) {
|
||||
log::error!("failed to save config_state: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -778,10 +777,10 @@ impl App {
|
|||
Some(DialogPage::PromptSaveQuit(ref _entities)) => {
|
||||
let mut unsaved = Vec::new();
|
||||
for entity in self.tab_model.iter() {
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity)
|
||||
&& tab.changed()
|
||||
{
|
||||
unsaved.push(entity);
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity) {
|
||||
if tab.changed() {
|
||||
unsaved.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
if unsaved.is_empty() {
|
||||
|
|
@ -920,16 +919,16 @@ impl App {
|
|||
}
|
||||
|
||||
'tabs: for entity in self.tab_model.iter() {
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity)
|
||||
&& let Some(path) = &tab.path_opt
|
||||
{
|
||||
for (_, project_path) in self.projects.iter() {
|
||||
if path.starts_with(project_path) {
|
||||
// Do not watch tabs inside of already watched projects
|
||||
continue 'tabs;
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity) {
|
||||
if let Some(path) = &tab.path_opt {
|
||||
for (_, project_path) in self.projects.iter() {
|
||||
if path.starts_with(&project_path) {
|
||||
// Do not watch tabs inside of already watched projects
|
||||
continue 'tabs;
|
||||
}
|
||||
}
|
||||
new_paths.insert((path.to_path_buf(), RecursiveMode::NonRecursive));
|
||||
}
|
||||
new_paths.insert((path.to_path_buf(), RecursiveMode::NonRecursive));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1444,7 +1443,7 @@ impl Application for App {
|
|||
|
||||
let mut theme_names =
|
||||
Vec::with_capacity(SYNTAX_SYSTEM.get().unwrap().theme_set.themes.len());
|
||||
for theme_name in SYNTAX_SYSTEM.get().unwrap().theme_set.themes.keys() {
|
||||
for (theme_name, _theme) in SYNTAX_SYSTEM.get().unwrap().theme_set.themes.iter() {
|
||||
theme_names.push(theme_name.to_string());
|
||||
}
|
||||
|
||||
|
|
@ -1463,7 +1462,7 @@ impl Application for App {
|
|||
.comments(fl!("comment"))
|
||||
.license("GPL-3.0-only")
|
||||
.license_url("https://spdx.org/licenses/GPL-3.0-only")
|
||||
.developers([("System76", "info@system76.com")])
|
||||
.developers([("Jeremy Soller", "jeremy@system76.com")])
|
||||
.links([
|
||||
(fl!("repository"), "https://github.com/pop-os/cosmic-edit"),
|
||||
(
|
||||
|
|
@ -1653,7 +1652,9 @@ impl Application for App {
|
|||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, Self::Message>> {
|
||||
let dialog = self.dialog_page_opt.as_ref()?;
|
||||
let Some(ref dialog) = self.dialog_page_opt else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
|
||||
|
||||
|
|
@ -1936,21 +1937,21 @@ impl Application for App {
|
|||
return self.update_config();
|
||||
}
|
||||
Message::FindNext => {
|
||||
if !self.find_search_value.is_empty()
|
||||
&& let Some(Tab::Editor(tab)) = self.active_tab()
|
||||
{
|
||||
//TODO: do not compile find regex on every search?
|
||||
match self.config.find_regex(&self.find_search_value) {
|
||||
Ok(regex) => {
|
||||
tab.search(®ex, true, self.config.find_wrap_around);
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: put regex error in find box
|
||||
log::warn!(
|
||||
"failed to compile regex {:?}: {}",
|
||||
self.find_search_value,
|
||||
err
|
||||
);
|
||||
if !self.find_search_value.is_empty() {
|
||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
||||
//TODO: do not compile find regex on every search?
|
||||
match self.config.find_regex(&self.find_search_value) {
|
||||
Ok(regex) => {
|
||||
tab.search(®ex, true, self.config.find_wrap_around);
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: put regex error in find box
|
||||
log::warn!(
|
||||
"failed to compile regex {:?}: {}",
|
||||
self.find_search_value,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1959,21 +1960,21 @@ impl Application for App {
|
|||
return self.update_focus();
|
||||
}
|
||||
Message::FindPrevious => {
|
||||
if !self.find_search_value.is_empty()
|
||||
&& let Some(Tab::Editor(tab)) = self.active_tab()
|
||||
{
|
||||
//TODO: do not compile find regex on every search?
|
||||
match self.config.find_regex(&self.find_search_value) {
|
||||
Ok(regex) => {
|
||||
tab.search(®ex, false, self.config.find_wrap_around);
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: put regex error in find box
|
||||
log::warn!(
|
||||
"failed to compile regex {:?}: {}",
|
||||
self.find_search_value,
|
||||
err
|
||||
);
|
||||
if !self.find_search_value.is_empty() {
|
||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
||||
//TODO: do not compile find regex on every search?
|
||||
match self.config.find_regex(&self.find_search_value) {
|
||||
Ok(regex) => {
|
||||
tab.search(®ex, false, self.config.find_wrap_around);
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: put regex error in find box
|
||||
log::warn!(
|
||||
"failed to compile regex {:?}: {}",
|
||||
self.find_search_value,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1982,27 +1983,27 @@ impl Application for App {
|
|||
return self.update_focus();
|
||||
}
|
||||
Message::FindReplace => {
|
||||
if !self.find_search_value.is_empty()
|
||||
&& let Some(Tab::Editor(tab)) = self.active_tab()
|
||||
{
|
||||
//TODO: do not compile find regex on every search?
|
||||
match self.config.find_regex(&self.find_search_value) {
|
||||
Ok(regex) => {
|
||||
//TODO: support captures
|
||||
tab.replace(
|
||||
®ex,
|
||||
&self.find_replace_value,
|
||||
self.config.find_wrap_around,
|
||||
);
|
||||
return self.update(Message::TabChanged(self.tab_model.active()));
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: put regex error in find box
|
||||
log::warn!(
|
||||
"failed to compile regex {:?}: {}",
|
||||
self.find_search_value,
|
||||
err
|
||||
);
|
||||
if !self.find_search_value.is_empty() {
|
||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
||||
//TODO: do not compile find regex on every search?
|
||||
match self.config.find_regex(&self.find_search_value) {
|
||||
Ok(regex) => {
|
||||
//TODO: support captures
|
||||
tab.replace(
|
||||
®ex,
|
||||
&self.find_replace_value,
|
||||
self.config.find_wrap_around,
|
||||
);
|
||||
return self.update(Message::TabChanged(self.tab_model.active()));
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: put regex error in find box
|
||||
log::warn!(
|
||||
"failed to compile regex {:?}: {}",
|
||||
self.find_search_value,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2011,27 +2012,27 @@ impl Application for App {
|
|||
return self.update_focus();
|
||||
}
|
||||
Message::FindReplaceAll => {
|
||||
if !self.find_search_value.is_empty()
|
||||
&& let Some(Tab::Editor(tab)) = self.active_tab()
|
||||
{
|
||||
//TODO: do not compile find regex on every search?
|
||||
match self.config.find_regex(&self.find_search_value) {
|
||||
Ok(regex) => {
|
||||
//TODO: support captures
|
||||
{
|
||||
let mut editor = tab.editor.lock().unwrap();
|
||||
editor.set_cursor(cosmic_text::Cursor::new(0, 0));
|
||||
if !self.find_search_value.is_empty() {
|
||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
||||
//TODO: do not compile find regex on every search?
|
||||
match self.config.find_regex(&self.find_search_value) {
|
||||
Ok(regex) => {
|
||||
//TODO: support captures
|
||||
{
|
||||
let mut editor = tab.editor.lock().unwrap();
|
||||
editor.set_cursor(cosmic_text::Cursor::new(0, 0));
|
||||
}
|
||||
while tab.replace(®ex, &self.find_replace_value, false) {}
|
||||
return self.update(Message::TabChanged(self.tab_model.active()));
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: put regex error in find box
|
||||
log::warn!(
|
||||
"failed to compile regex {:?}: {}",
|
||||
self.find_search_value,
|
||||
err
|
||||
);
|
||||
}
|
||||
while tab.replace(®ex, &self.find_replace_value, false) {}
|
||||
return self.update(Message::TabChanged(self.tab_model.active()));
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: put regex error in find box
|
||||
log::warn!(
|
||||
"failed to compile regex {:?}: {}",
|
||||
self.find_search_value,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2171,14 +2172,18 @@ impl Application for App {
|
|||
// Reload tabs that changed
|
||||
let mut tab_reload = Vec::new();
|
||||
for entity in self.tab_model.iter() {
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity)
|
||||
&& let Some(path) = &tab.path_opt
|
||||
&& event.paths.contains(path)
|
||||
{
|
||||
if tab.changed() {
|
||||
log::warn!("file changed externally before being saved: {:?}", path);
|
||||
} else {
|
||||
tab_reload.push(entity);
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity) {
|
||||
if let Some(path) = &tab.path_opt {
|
||||
if event.paths.contains(path) {
|
||||
if tab.changed() {
|
||||
log::warn!(
|
||||
"file changed externally before being saved: {:?}",
|
||||
path
|
||||
);
|
||||
} else {
|
||||
tab_reload.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2276,7 +2281,7 @@ impl Application for App {
|
|||
{
|
||||
for (_, project_path) in self.projects.iter() {
|
||||
for path in event.paths.iter() {
|
||||
if let Ok(prefix) = path.strip_prefix(project_path) {
|
||||
if let Ok(prefix) = path.strip_prefix(&project_path) {
|
||||
// Manually ignore project .git folders
|
||||
//TODO: use logic from ignore crate somehow?
|
||||
if prefix.starts_with(".git") {
|
||||
|
|
@ -2346,10 +2351,10 @@ impl Application for App {
|
|||
{
|
||||
let mut close = Vec::new();
|
||||
for entity in self.tab_model.iter() {
|
||||
if let Some(Tab::GitDiff(other_tab)) = self.tab_model.data::<Tab>(entity)
|
||||
&& other_tab.diff.path == diff.path
|
||||
{
|
||||
close.push(entity);
|
||||
if let Some(Tab::GitDiff(other_tab)) = self.tab_model.data::<Tab>(entity) {
|
||||
if other_tab.diff.path == diff.path {
|
||||
close.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
for entity in close {
|
||||
|
|
@ -2667,10 +2672,10 @@ impl Application for App {
|
|||
tab.path_opt = Some(paths.remove(0));
|
||||
title_opt = Some(tab.title());
|
||||
tab.save();
|
||||
if let Some(path) = tab.path_opt.clone()
|
||||
&& let Ok(canonical) = fs::canonicalize(&path)
|
||||
{
|
||||
self.add_to_recents(&canonical);
|
||||
if let Some(path) = tab.path_opt.clone() {
|
||||
if let Ok(canonical) = fs::canonicalize(&path) {
|
||||
self.add_to_recents(&canonical);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(title) = title_opt {
|
||||
|
|
@ -2708,7 +2713,9 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
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) => {
|
||||
return self.update_config();
|
||||
|
|
@ -2816,15 +2823,15 @@ impl Application for App {
|
|||
Message::TabContextAction(entity, action) => {
|
||||
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
// Close context menu
|
||||
tab.context_menu_open = false;
|
||||
tab.context_menu = None;
|
||||
// Run action's message
|
||||
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) {
|
||||
// Update context menu
|
||||
tab.context_menu_open = open;
|
||||
tab.context_menu = position_opt;
|
||||
}
|
||||
}
|
||||
Message::TabNext => {
|
||||
|
|
@ -2846,7 +2853,13 @@ impl Application for App {
|
|||
.tab_model
|
||||
.position(self.tab_model.active())
|
||||
.and_then(|i| (i as usize).checked_sub(1))
|
||||
.unwrap_or_else(|| self.tab_model.iter().count().saturating_sub(1));
|
||||
.unwrap_or_else(|| {
|
||||
self.tab_model
|
||||
.iter()
|
||||
.count()
|
||||
.checked_sub(1)
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let entity = self.tab_model.iter().nth(pos);
|
||||
if let Some(entity) = entity {
|
||||
|
|
@ -3080,25 +3093,24 @@ impl Application for App {
|
|||
.on_focus(Message::FindFocused(false))
|
||||
.on_auto_scroll(Message::AutoScroll)
|
||||
.on_changed(Message::TabChanged(tab_id))
|
||||
.has_context_menu(tab.context_menu_open)
|
||||
.on_context_menu(move |open| Message::TabContextMenu(tab_id, open));
|
||||
.has_context_menu(tab.context_menu.is_some())
|
||||
.on_context_menu(move |position_opt| {
|
||||
Message::TabContextMenu(tab_id, position_opt)
|
||||
});
|
||||
if self.config.highlight_current_line {
|
||||
text_box = text_box.highlight_current_line();
|
||||
}
|
||||
if self.config.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 mut context_menu = widget::context_menu(
|
||||
text_box,
|
||||
Some(menu::context_menu(&self.key_binds, tab_id, has_selection)),
|
||||
)
|
||||
.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);
|
||||
if let Some(point) = tab.context_menu {
|
||||
popover = popover
|
||||
.popup(menu::context_menu(&self.key_binds, tab_id, has_selection))
|
||||
.position(widget::popover::Position::Point(point));
|
||||
}
|
||||
tab_column = tab_column.push(context_menu);
|
||||
tab_column = tab_column.push(popover);
|
||||
if self.config.vim_bindings {
|
||||
let status = {
|
||||
let editor = tab.editor.lock().unwrap();
|
||||
|
|
|
|||
125
src/menu.rs
125
src/menu.rs
|
|
@ -1,22 +1,21 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::widget::menu::Item as MenuItem;
|
||||
use cosmic::widget::menu::action::MenuAction;
|
||||
use cosmic::widget::menu::key_bind::KeyBind;
|
||||
use cosmic::{
|
||||
Element,
|
||||
app::Core,
|
||||
iced::{
|
||||
Background, Border, Length, advanced::widget::text::Style as TextStyle, widget::column,
|
||||
},
|
||||
theme,
|
||||
widget::{
|
||||
self,
|
||||
menu::{ItemHeight, ItemWidth},
|
||||
responsive_menu_bar, segmented_button,
|
||||
self, divider,
|
||||
menu::{ItemHeight, ItemWidth, menu_button},
|
||||
responsive_menu_bar, segmented_button, space,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::LazyLock,
|
||||
};
|
||||
use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
|
||||
|
||||
use crate::{Action, Config, ConfigState, Message, fl};
|
||||
|
||||
|
|
@ -64,7 +63,7 @@ fn truncate_middle(value: &str, max_chars: usize) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
fn format_recent_menu_path(path: &Path, home_dir_opt: Option<&PathBuf>) -> String {
|
||||
fn format_recent_menu_path(path: &PathBuf, home_dir_opt: Option<&PathBuf>) -> String {
|
||||
const ELLIPSIS: &str = "...";
|
||||
|
||||
let display = if let Some(home_dir) = home_dir_opt {
|
||||
|
|
@ -117,49 +116,81 @@ fn format_recent_menu_path(path: &Path, home_dir_opt: Option<&PathBuf>) -> Strin
|
|||
truncate_middle(&display, RECENT_MENU_LABEL_MAX_CHARS)
|
||||
}
|
||||
|
||||
/// A context menu action dispatched to the tab the menu was opened on.
|
||||
#[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(
|
||||
pub fn context_menu<'a>(
|
||||
key_binds: &HashMap<KeyBind, Action>,
|
||||
entity: segmented_button::Entity,
|
||||
has_selection: bool,
|
||||
) -> Vec<widget::menu::Tree<Message>> {
|
||||
let item =
|
||||
|label: String, action: Action| MenuItem::Button(label, None, TabAction(entity, action));
|
||||
let optional = |label: String, action: Action, enabled: bool| {
|
||||
if enabled {
|
||||
MenuItem::Button(label, None, TabAction(entity, action))
|
||||
} else {
|
||||
MenuItem::ButtonDisabled(label, None, TabAction(entity, action))
|
||||
) -> Element<'a, Message> {
|
||||
fn key_style(theme: &cosmic::Theme) -> TextStyle {
|
||||
// TODO use wayland popups
|
||||
let mut color = theme.cosmic().background(false).component.on;
|
||||
color.alpha *= 0.75;
|
||||
TextStyle {
|
||||
color: Some(color.into()),
|
||||
..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
|
||||
.iter()
|
||||
.map(|(key_bind, action)| (key_bind.clone(), TabAction(entity, *action)))
|
||||
.collect();
|
||||
widget::menu::items(
|
||||
&key_binds,
|
||||
vec![
|
||||
item(fl!("undo"), Action::Undo),
|
||||
item(fl!("redo"), Action::Redo),
|
||||
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),
|
||||
],
|
||||
let optional_menu_item = |menu_label, menu_action, disabled| {
|
||||
base_menu_item(menu_label, menu_action).on_press_maybe(if disabled {
|
||||
None
|
||||
} else {
|
||||
Some(Message::TabContextAction(entity, menu_action))
|
||||
})
|
||||
};
|
||||
|
||||
let menu_item = |menu_label, menu_action| {
|
||||
base_menu_item(menu_label, menu_action)
|
||||
.on_press(Message::TabContextAction(entity, menu_action))
|
||||
};
|
||||
|
||||
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>(
|
||||
|
|
@ -167,7 +198,7 @@ pub fn menu_bar<'a>(
|
|||
config: &Config,
|
||||
config_state: &ConfigState,
|
||||
key_binds: &HashMap<KeyBind, Action>,
|
||||
projects: &[(String, PathBuf)],
|
||||
projects: &Vec<(String, PathBuf)>,
|
||||
has_selection: bool,
|
||||
) -> Element<'a, Message> {
|
||||
//TODO: port to libcosmic
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ impl ProjectNode {
|
|||
let path = fs::canonicalize(path)?;
|
||||
let name = path
|
||||
.file_name()
|
||||
.ok_or(io::Error::other(format!(
|
||||
"path {:?} has no file name",
|
||||
path
|
||||
)))?
|
||||
.ok_or(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("path {:?} has no file name", path),
|
||||
))?
|
||||
.to_str()
|
||||
.ok_or(io::Error::other(format!(
|
||||
"path {:?} is not valid UTF-8",
|
||||
path
|
||||
)))?
|
||||
.ok_or(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("path {:?} is not valid UTF-8", path),
|
||||
))?
|
||||
.to_string();
|
||||
Ok(if path.is_dir() {
|
||||
Self::Folder {
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@ impl ProjectSearchResult {
|
|||
}
|
||||
};
|
||||
|
||||
if let Some(file_type) = entry.file_type()
|
||||
&& file_type.is_dir()
|
||||
{
|
||||
continue;
|
||||
if let Some(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let entry_path = entry.path();
|
||||
|
|
|
|||
16
src/tab.rs
16
src/tab.rs
|
|
@ -1,6 +1,9 @@
|
|||
// 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_text::{Attrs, Buffer, Cursor, Edit, Selection, Shaping, SyntaxEditor, ViEditor, Wrap};
|
||||
use regex::Regex;
|
||||
|
|
@ -25,7 +28,6 @@ fn editor_text(editor: &ViEditor<'static, 'static>) -> String {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Tab {
|
||||
Editor(EditorTab),
|
||||
GitDiff(GitDiffTab),
|
||||
|
|
@ -49,7 +51,7 @@ pub struct EditorTab {
|
|||
pub path_opt: Option<PathBuf>,
|
||||
attrs: Attrs<'static>,
|
||||
pub editor: Mutex<ViEditor<'static, 'static>>,
|
||||
pub context_menu_open: bool,
|
||||
pub context_menu: Option<Point>,
|
||||
pub zoom_adj: i8,
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +75,7 @@ impl EditorTab {
|
|||
path_opt: None,
|
||||
attrs,
|
||||
editor: Mutex::new(ViEditor::new(editor)),
|
||||
context_menu_open: false,
|
||||
context_menu: None,
|
||||
zoom_adj,
|
||||
};
|
||||
|
||||
|
|
@ -314,7 +316,7 @@ impl EditorTab {
|
|||
.filter_map(|m| {
|
||||
if cursor.line != start_line
|
||||
|| m.start() >= cursor.index
|
||||
|| m.start() < cursor.index && wrapped
|
||||
|| m.start() < cursor.index && wrapped == true
|
||||
{
|
||||
Some((m.start(), m.len()))
|
||||
} else {
|
||||
|
|
@ -391,7 +393,7 @@ impl EditorTab {
|
|||
if cursor.line != start_line
|
||||
|| m.start() > cursor.index
|
||||
|| m.start() == cursor.index && current_selection == Selection::None
|
||||
|| m.start() < cursor.index && wrapped
|
||||
|| m.start() < cursor.index && wrapped == true
|
||||
{
|
||||
Some((m.start(), m.end()))
|
||||
} else {
|
||||
|
|
@ -434,7 +436,7 @@ impl EditorTab {
|
|||
if cursor.line != start_line
|
||||
|| m.start() < cursor.index
|
||||
|| m.start() == cursor.index && current_selection == Selection::None
|
||||
|| m.start() > cursor.index && wrapped
|
||||
|| m.start() > cursor.index && wrapped == true
|
||||
{
|
||||
Some((m.start(), m.end()))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ pub struct TextBox<'a, Message> {
|
|||
on_focus: Option<Message>,
|
||||
click_timing: Duration,
|
||||
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,
|
||||
line_numbers: bool,
|
||||
}
|
||||
|
|
@ -107,7 +107,10 @@ where
|
|||
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
|
||||
}
|
||||
|
|
@ -317,8 +320,9 @@ where
|
|||
editor.with_buffer(|buffer| {
|
||||
let mut layout_lines = 0;
|
||||
for line in buffer.lines.iter() {
|
||||
if let Some(layout) = line.layout_opt() {
|
||||
layout_lines += layout.len()
|
||||
match line.layout_opt() {
|
||||
Some(layout) => layout_lines += layout.len(),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -754,7 +758,7 @@ where
|
|||
match editor.buffer_ref() {
|
||||
cosmic_text::BufferRef::Arc(buffer) => {
|
||||
renderer.fill_raw(Raw {
|
||||
buffer: Arc::downgrade(buffer),
|
||||
buffer: Arc::downgrade(&buffer),
|
||||
position: pos,
|
||||
color: Color::from_rgba(1.0, 1.0, 1.0, 1.0),
|
||||
clip_bounds,
|
||||
|
|
@ -1094,13 +1098,15 @@ where
|
|||
editor.set_redraw(true);
|
||||
shell.capture_event();
|
||||
}
|
||||
Named::Tab if !modifiers.control() && !modifiers.alt() => {
|
||||
if modifiers.shift() {
|
||||
editor.action(Action::Unindent);
|
||||
} else {
|
||||
editor.action(Action::Indent);
|
||||
Named::Tab => {
|
||||
if !modifiers.control() && !modifiers.alt() {
|
||||
if modifiers.shift() {
|
||||
editor.action(Action::Unindent);
|
||||
} else {
|
||||
editor.action(Action::Indent);
|
||||
}
|
||||
shell.capture_event();
|
||||
}
|
||||
shell.capture_event();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
|
|
@ -1149,7 +1155,7 @@ where
|
|||
InputMethodEvent::Commit(text) => {
|
||||
if state.is_focused {
|
||||
editor.start_change();
|
||||
editor.insert_string(text, None);
|
||||
editor.insert_string(&text, None);
|
||||
editor.finish_change();
|
||||
shell.capture_event();
|
||||
}
|
||||
|
|
@ -1171,13 +1177,13 @@ where
|
|||
let y = y_logical * scale_factor;
|
||||
|
||||
// Do this first as the horizontal scrollbar is on top of the buffer
|
||||
if let Some(scrollbar_h_rect) = state.scrollbar_h_rect.get()
|
||||
&& scrollbar_h_rect.contains(Point::new(x_logical, y_logical))
|
||||
{
|
||||
state.dragging = Some(Dragging::ScrollbarH {
|
||||
start_x: x,
|
||||
start_scroll: editor.with_buffer(|buffer| buffer.scroll()),
|
||||
});
|
||||
if let Some(scrollbar_h_rect) = state.scrollbar_h_rect.get() {
|
||||
if scrollbar_h_rect.contains(Point::new(x_logical, y_logical)) {
|
||||
state.dragging = Some(Dragging::ScrollbarH {
|
||||
start_x: x,
|
||||
start_scroll: editor.with_buffer(|buffer| buffer.scroll()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(state.dragging, Some(Dragging::ScrollbarH { .. })) {
|
||||
|
|
@ -1204,7 +1210,7 @@ where
|
|||
ClickKind::Single
|
||||
};
|
||||
let maybe_anchor = if state.modifiers.shift() {
|
||||
*state.shift_anchor.lock().unwrap()
|
||||
state.shift_anchor.lock().unwrap().clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -1261,9 +1267,14 @@ where
|
|||
|
||||
// Update context menu state
|
||||
if let Some(on_context_menu) = &self.on_context_menu {
|
||||
shell.publish((on_context_menu)(
|
||||
!self.has_context_menu && matches!(button, Button::Right),
|
||||
));
|
||||
shell.publish((on_context_menu)(if self.has_context_menu {
|
||||
None
|
||||
} else {
|
||||
match button {
|
||||
Button::Right => Some(p),
|
||||
_ => None,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
shell.capture_event();
|
||||
|
|
@ -1385,9 +1396,11 @@ where
|
|||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Window(WindowEvent::RedrawRequested(_now)) if state.is_focused => {
|
||||
state.caret_position = editor.cursor_position().unwrap_or(state.caret_position);
|
||||
shell.request_input_method(&self.input_method(state, scale_factor, layout));
|
||||
Event::Window(WindowEvent::RedrawRequested(_now)) => {
|
||||
if state.is_focused {
|
||||
state.caret_position = editor.cursor_position().unwrap_or(state.caret_position);
|
||||
shell.request_input_method(&self.input_method(state, scale_factor, layout));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue