merge: epoch-1.10.0 d'upstream
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
79f6777d08
17 changed files with 704 additions and 438 deletions
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,8 +1,10 @@
|
||||||
- [ ] I have disclosed use of any AI generated code in my commit messages.
|
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.
|
||||||
- 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
Normal file
47
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
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
Normal file
14
.github/workflows/require-checklist.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# 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
|
||||||
632
Cargo.lock
generated
632
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-edit"
|
name = "cosmic-edit"
|
||||||
version = "1.9.0"
|
version = "1.10.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"
|
||||||
|
|
|
||||||
70
cargo.just
Normal file
70
cargo.just
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
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
|
||||||
6
debian/changelog
vendored
6
debian/changelog
vendored
|
|
@ -1,3 +1,9 @@
|
||||||
|
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
|
cosmic-edit (1.9.0) noble; urgency=medium
|
||||||
|
|
||||||
* Epoch 1.9.0 version update
|
* Epoch 1.9.0 version update
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ dark = Sötét
|
||||||
light = Világos
|
light = Világos
|
||||||
syntax-dark = Sötét szintaxis
|
syntax-dark = Sötét szintaxis
|
||||||
syntax-light = Világos szintaxis
|
syntax-light = Világos szintaxis
|
||||||
default-font = Alapértelmezett betűtípus
|
default-font = Alapértelmezett betűkészlet
|
||||||
default-font-size = Alapértelmezett betűméret
|
default-font-size = Alapértelmezett betűméret
|
||||||
default-zoom-step = Nagyítási mérték
|
default-zoom-step = Nagyítási mérték
|
||||||
|
|
||||||
|
|
|
||||||
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "1.93"
|
||||||
|
components = ["clippy", "rustfmt"]
|
||||||
|
|
@ -112,17 +112,8 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize, Default)]
|
||||||
pub struct ConfigState {
|
pub struct ConfigState {
|
||||||
pub recent_files: VecDeque<PathBuf>,
|
pub recent_files: VecDeque<PathBuf>,
|
||||||
pub recent_projects: 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("\nstderr> ");
|
||||||
msg.push_str(line);
|
msg.push_str(line);
|
||||||
}
|
}
|
||||||
Err(io::Error::new(io::ErrorKind::Other, msg))
|
Err(io::Error::other(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
103
src/main.rs
103
src/main.rs
|
|
@ -328,6 +328,7 @@ impl PartialEq for WatcherWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
enum NewTab {
|
enum NewTab {
|
||||||
Tab(EditorTab),
|
Tab(EditorTab),
|
||||||
Exists(Entity),
|
Exists(Entity),
|
||||||
|
|
@ -529,7 +530,7 @@ impl App {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let entry_path = entry.path();
|
let entry_path = entry.path();
|
||||||
let node = match ProjectNode::new(&entry_path) {
|
let node = match ProjectNode::new(entry_path) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!(
|
log::error!(
|
||||||
|
|
@ -682,13 +683,13 @@ impl App {
|
||||||
//TODO: allow files to be open multiple times
|
//TODO: allow files to be open multiple times
|
||||||
let mut activate_opt = None;
|
let mut activate_opt = None;
|
||||||
for entity in self.tab_model.iter() {
|
for entity in self.tab_model.iter() {
|
||||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity) {
|
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity)
|
||||||
if tab.path_opt.as_ref() == Some(&canonical) {
|
&& tab.path_opt.as_ref() == Some(&canonical)
|
||||||
|
{
|
||||||
activate_opt = Some(entity);
|
activate_opt = Some(entity);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let Some(entity) = activate_opt {
|
if let Some(entity) = activate_opt {
|
||||||
self.tab_model.activate(entity);
|
self.tab_model.activate(entity);
|
||||||
return Some(NewTab::Exists(entity));
|
return Some(NewTab::Exists(entity));
|
||||||
|
|
@ -734,13 +735,13 @@ impl App {
|
||||||
}
|
}
|
||||||
let entities: Vec<_> = self.tab_model.iter().collect();
|
let entities: Vec<_> = self.tab_model.iter().collect();
|
||||||
for entity in entities {
|
for entity in entities {
|
||||||
if self.tab_model.is_active(entity) {
|
if self.tab_model.is_active(entity)
|
||||||
if let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity) {
|
&& let Some(Tab::Editor(tab)) = self.tab_model.data_mut::<Tab>(entity)
|
||||||
|
{
|
||||||
tab.set_config(&self.config);
|
tab.set_config(&self.config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -754,12 +755,12 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_config_state(&mut self) {
|
fn save_config_state(&mut self) {
|
||||||
if let Some(ref config_state_handler) = self.config_state_handler {
|
if let Some(ref config_state_handler) = self.config_state_handler
|
||||||
if let Err(err) = self.config_state.write_entry(config_state_handler) {
|
&& let Err(err) = self.config_state.write_entry(config_state_handler)
|
||||||
|
{
|
||||||
log::error!("failed to save config_state: {}", err);
|
log::error!("failed to save config_state: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn update_dialogs(&mut self) -> Task<Message> {
|
fn update_dialogs(&mut self) -> Task<Message> {
|
||||||
match self.dialog_page_opt {
|
match self.dialog_page_opt {
|
||||||
|
|
@ -777,12 +778,12 @@ impl App {
|
||||||
Some(DialogPage::PromptSaveQuit(ref _entities)) => {
|
Some(DialogPage::PromptSaveQuit(ref _entities)) => {
|
||||||
let mut unsaved = Vec::new();
|
let mut unsaved = Vec::new();
|
||||||
for entity in self.tab_model.iter() {
|
for entity in self.tab_model.iter() {
|
||||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity) {
|
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity)
|
||||||
if tab.changed() {
|
&& tab.changed()
|
||||||
|
{
|
||||||
unsaved.push(entity);
|
unsaved.push(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if unsaved.is_empty() {
|
if unsaved.is_empty() {
|
||||||
// All tabs have been saved, we can exit
|
// All tabs have been saved, we can exit
|
||||||
return self.update(Message::QuitForce);
|
return self.update(Message::QuitForce);
|
||||||
|
|
@ -919,10 +920,11 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
'tabs: for entity in self.tab_model.iter() {
|
'tabs: for entity in self.tab_model.iter() {
|
||||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity) {
|
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity)
|
||||||
if let Some(path) = &tab.path_opt {
|
&& let Some(path) = &tab.path_opt
|
||||||
|
{
|
||||||
for (_, project_path) in self.projects.iter() {
|
for (_, project_path) in self.projects.iter() {
|
||||||
if path.starts_with(&project_path) {
|
if path.starts_with(project_path) {
|
||||||
// Do not watch tabs inside of already watched projects
|
// Do not watch tabs inside of already watched projects
|
||||||
continue 'tabs;
|
continue 'tabs;
|
||||||
}
|
}
|
||||||
|
|
@ -930,7 +932,6 @@ impl App {
|
||||||
new_paths.insert((path.to_path_buf(), RecursiveMode::NonRecursive));
|
new_paths.insert((path.to_path_buf(), RecursiveMode::NonRecursive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Unwatch paths no longer used
|
// Unwatch paths no longer used
|
||||||
for path_mode in old_paths.iter() {
|
for path_mode in old_paths.iter() {
|
||||||
|
|
@ -1443,7 +1444,7 @@ impl Application for App {
|
||||||
|
|
||||||
let mut theme_names =
|
let mut theme_names =
|
||||||
Vec::with_capacity(SYNTAX_SYSTEM.get().unwrap().theme_set.themes.len());
|
Vec::with_capacity(SYNTAX_SYSTEM.get().unwrap().theme_set.themes.len());
|
||||||
for (theme_name, _theme) in SYNTAX_SYSTEM.get().unwrap().theme_set.themes.iter() {
|
for theme_name in SYNTAX_SYSTEM.get().unwrap().theme_set.themes.keys() {
|
||||||
theme_names.push(theme_name.to_string());
|
theme_names.push(theme_name.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1462,7 +1463,7 @@ impl Application for App {
|
||||||
.comments(fl!("comment"))
|
.comments(fl!("comment"))
|
||||||
.license("GPL-3.0-only")
|
.license("GPL-3.0-only")
|
||||||
.license_url("https://spdx.org/licenses/GPL-3.0-only")
|
.license_url("https://spdx.org/licenses/GPL-3.0-only")
|
||||||
.developers([("Jeremy Soller", "jeremy@system76.com")])
|
.developers([("System76", "info@system76.com")])
|
||||||
.links([
|
.links([
|
||||||
(fl!("repository"), "https://github.com/pop-os/cosmic-edit"),
|
(fl!("repository"), "https://github.com/pop-os/cosmic-edit"),
|
||||||
(
|
(
|
||||||
|
|
@ -1652,9 +1653,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dialog(&self) -> Option<Element<'_, Self::Message>> {
|
fn dialog(&self) -> Option<Element<'_, Self::Message>> {
|
||||||
let Some(ref dialog) = self.dialog_page_opt else {
|
let dialog = self.dialog_page_opt.as_ref()?;
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
|
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
|
||||||
|
|
||||||
|
|
@ -1937,8 +1936,9 @@ impl Application for App {
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
}
|
}
|
||||||
Message::FindNext => {
|
Message::FindNext => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty()
|
||||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
&& let Some(Tab::Editor(tab)) = self.active_tab()
|
||||||
|
{
|
||||||
//TODO: do not compile find regex on every search?
|
//TODO: do not compile find regex on every search?
|
||||||
match self.config.find_regex(&self.find_search_value) {
|
match self.config.find_regex(&self.find_search_value) {
|
||||||
Ok(regex) => {
|
Ok(regex) => {
|
||||||
|
|
@ -1954,14 +1954,14 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Focus correct input
|
// Focus correct input
|
||||||
return self.update_focus();
|
return self.update_focus();
|
||||||
}
|
}
|
||||||
Message::FindPrevious => {
|
Message::FindPrevious => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty()
|
||||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
&& let Some(Tab::Editor(tab)) = self.active_tab()
|
||||||
|
{
|
||||||
//TODO: do not compile find regex on every search?
|
//TODO: do not compile find regex on every search?
|
||||||
match self.config.find_regex(&self.find_search_value) {
|
match self.config.find_regex(&self.find_search_value) {
|
||||||
Ok(regex) => {
|
Ok(regex) => {
|
||||||
|
|
@ -1977,14 +1977,14 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Focus correct input
|
// Focus correct input
|
||||||
return self.update_focus();
|
return self.update_focus();
|
||||||
}
|
}
|
||||||
Message::FindReplace => {
|
Message::FindReplace => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty()
|
||||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
&& let Some(Tab::Editor(tab)) = self.active_tab()
|
||||||
|
{
|
||||||
//TODO: do not compile find regex on every search?
|
//TODO: do not compile find regex on every search?
|
||||||
match self.config.find_regex(&self.find_search_value) {
|
match self.config.find_regex(&self.find_search_value) {
|
||||||
Ok(regex) => {
|
Ok(regex) => {
|
||||||
|
|
@ -2006,14 +2006,14 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Focus correct input
|
// Focus correct input
|
||||||
return self.update_focus();
|
return self.update_focus();
|
||||||
}
|
}
|
||||||
Message::FindReplaceAll => {
|
Message::FindReplaceAll => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty()
|
||||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
&& let Some(Tab::Editor(tab)) = self.active_tab()
|
||||||
|
{
|
||||||
//TODO: do not compile find regex on every search?
|
//TODO: do not compile find regex on every search?
|
||||||
match self.config.find_regex(&self.find_search_value) {
|
match self.config.find_regex(&self.find_search_value) {
|
||||||
Ok(regex) => {
|
Ok(regex) => {
|
||||||
|
|
@ -2035,7 +2035,6 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Focus correct input
|
// Focus correct input
|
||||||
return self.update_focus();
|
return self.update_focus();
|
||||||
|
|
@ -2172,21 +2171,17 @@ impl Application for App {
|
||||||
// Reload tabs that changed
|
// Reload tabs that changed
|
||||||
let mut tab_reload = Vec::new();
|
let mut tab_reload = Vec::new();
|
||||||
for entity in self.tab_model.iter() {
|
for entity in self.tab_model.iter() {
|
||||||
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity) {
|
if let Some(Tab::Editor(tab)) = self.tab_model.data::<Tab>(entity)
|
||||||
if let Some(path) = &tab.path_opt {
|
&& let Some(path) = &tab.path_opt
|
||||||
if event.paths.contains(path) {
|
&& event.paths.contains(path)
|
||||||
|
{
|
||||||
if tab.changed() {
|
if tab.changed() {
|
||||||
log::warn!(
|
log::warn!("file changed externally before being saved: {:?}", path);
|
||||||
"file changed externally before being saved: {:?}",
|
|
||||||
path
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
tab_reload.push(entity);
|
tab_reload.push(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
for entity in tab_reload {
|
for entity in tab_reload {
|
||||||
match self.tab_model.data_mut::<Tab>(entity) {
|
match self.tab_model.data_mut::<Tab>(entity) {
|
||||||
Some(Tab::Editor(tab)) => {
|
Some(Tab::Editor(tab)) => {
|
||||||
|
|
@ -2281,7 +2276,7 @@ impl Application for App {
|
||||||
{
|
{
|
||||||
for (_, project_path) in self.projects.iter() {
|
for (_, project_path) in self.projects.iter() {
|
||||||
for path in event.paths.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
|
// Manually ignore project .git folders
|
||||||
//TODO: use logic from ignore crate somehow?
|
//TODO: use logic from ignore crate somehow?
|
||||||
if prefix.starts_with(".git") {
|
if prefix.starts_with(".git") {
|
||||||
|
|
@ -2351,12 +2346,12 @@ impl Application for App {
|
||||||
{
|
{
|
||||||
let mut close = Vec::new();
|
let mut close = Vec::new();
|
||||||
for entity in self.tab_model.iter() {
|
for entity in self.tab_model.iter() {
|
||||||
if let Some(Tab::GitDiff(other_tab)) = self.tab_model.data::<Tab>(entity) {
|
if let Some(Tab::GitDiff(other_tab)) = self.tab_model.data::<Tab>(entity)
|
||||||
if other_tab.diff.path == diff.path {
|
&& other_tab.diff.path == diff.path
|
||||||
|
{
|
||||||
close.push(entity);
|
close.push(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for entity in close {
|
for entity in close {
|
||||||
self.tab_model.remove(entity);
|
self.tab_model.remove(entity);
|
||||||
}
|
}
|
||||||
|
|
@ -2672,12 +2667,12 @@ impl Application for App {
|
||||||
tab.path_opt = Some(paths.remove(0));
|
tab.path_opt = Some(paths.remove(0));
|
||||||
title_opt = Some(tab.title());
|
title_opt = Some(tab.title());
|
||||||
tab.save();
|
tab.save();
|
||||||
if let Some(path) = tab.path_opt.clone() {
|
if let Some(path) = tab.path_opt.clone()
|
||||||
if let Ok(canonical) = fs::canonicalize(&path) {
|
&& let Ok(canonical) = fs::canonicalize(&path)
|
||||||
|
{
|
||||||
self.add_to_recents(&canonical);
|
self.add_to_recents(&canonical);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let Some(title) = title_opt {
|
if let Some(title) = title_opt {
|
||||||
self.tab_model.text_set(entity, title);
|
self.tab_model.text_set(entity, title);
|
||||||
}
|
}
|
||||||
|
|
@ -2851,13 +2846,7 @@ impl Application for App {
|
||||||
.tab_model
|
.tab_model
|
||||||
.position(self.tab_model.active())
|
.position(self.tab_model.active())
|
||||||
.and_then(|i| (i as usize).checked_sub(1))
|
.and_then(|i| (i as usize).checked_sub(1))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| self.tab_model.iter().count().saturating_sub(1));
|
||||||
self.tab_model
|
|
||||||
.iter()
|
|
||||||
.count()
|
|
||||||
.checked_sub(1)
|
|
||||||
.unwrap_or_default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let entity = self.tab_model.iter().nth(pos);
|
let entity = self.tab_model.iter().nth(pos);
|
||||||
if let Some(entity) = entity {
|
if let Some(entity) = entity {
|
||||||
|
|
|
||||||
10
src/menu.rs
10
src/menu.rs
|
|
@ -12,7 +12,11 @@ use cosmic::{
|
||||||
responsive_menu_bar, segmented_button,
|
responsive_menu_bar, segmented_button,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{Action, Config, ConfigState, Message, fl};
|
use crate::{Action, Config, ConfigState, Message, fl};
|
||||||
|
|
||||||
|
|
@ -60,7 +64,7 @@ fn truncate_middle(value: &str, max_chars: usize) -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_recent_menu_path(path: &PathBuf, home_dir_opt: Option<&PathBuf>) -> String {
|
fn format_recent_menu_path(path: &Path, home_dir_opt: Option<&PathBuf>) -> String {
|
||||||
const ELLIPSIS: &str = "...";
|
const ELLIPSIS: &str = "...";
|
||||||
|
|
||||||
let display = if let Some(home_dir) = home_dir_opt {
|
let display = if let Some(home_dir) = home_dir_opt {
|
||||||
|
|
@ -163,7 +167,7 @@ pub fn menu_bar<'a>(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
config_state: &ConfigState,
|
config_state: &ConfigState,
|
||||||
key_binds: &HashMap<KeyBind, Action>,
|
key_binds: &HashMap<KeyBind, Action>,
|
||||||
projects: &Vec<(String, PathBuf)>,
|
projects: &[(String, PathBuf)],
|
||||||
has_selection: bool,
|
has_selection: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
//TODO: port to libcosmic
|
//TODO: port to libcosmic
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,15 @@ impl ProjectNode {
|
||||||
let path = fs::canonicalize(path)?;
|
let path = fs::canonicalize(path)?;
|
||||||
let name = path
|
let name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.ok_or(io::Error::new(
|
.ok_or(io::Error::other(format!(
|
||||||
io::ErrorKind::Other,
|
"path {:?} has no file name",
|
||||||
format!("path {:?} has no file name", path),
|
path
|
||||||
))?
|
)))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or(io::Error::new(
|
.ok_or(io::Error::other(format!(
|
||||||
io::ErrorKind::Other,
|
"path {:?} is not valid UTF-8",
|
||||||
format!("path {:?} is not valid UTF-8", path),
|
path
|
||||||
))?
|
)))?
|
||||||
.to_string();
|
.to_string();
|
||||||
Ok(if path.is_dir() {
|
Ok(if path.is_dir() {
|
||||||
Self::Folder {
|
Self::Folder {
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@ impl ProjectSearchResult {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(file_type) = entry.file_type() {
|
if let Some(file_type) = entry.file_type()
|
||||||
if file_type.is_dir() {
|
&& file_type.is_dir()
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let entry_path = entry.path();
|
let entry_path = entry.path();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ fn editor_text(editor: &ViEditor<'static, 'static>) -> String {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum Tab {
|
pub enum Tab {
|
||||||
Editor(EditorTab),
|
Editor(EditorTab),
|
||||||
GitDiff(GitDiffTab),
|
GitDiff(GitDiffTab),
|
||||||
|
|
@ -313,7 +314,7 @@ impl EditorTab {
|
||||||
.filter_map(|m| {
|
.filter_map(|m| {
|
||||||
if cursor.line != start_line
|
if cursor.line != start_line
|
||||||
|| m.start() >= cursor.index
|
|| m.start() >= cursor.index
|
||||||
|| m.start() < cursor.index && wrapped == true
|
|| m.start() < cursor.index && wrapped
|
||||||
{
|
{
|
||||||
Some((m.start(), m.len()))
|
Some((m.start(), m.len()))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -390,7 +391,7 @@ impl EditorTab {
|
||||||
if cursor.line != start_line
|
if cursor.line != start_line
|
||||||
|| m.start() > cursor.index
|
|| m.start() > cursor.index
|
||||||
|| m.start() == cursor.index && current_selection == Selection::None
|
|| m.start() == cursor.index && current_selection == Selection::None
|
||||||
|| m.start() < cursor.index && wrapped == true
|
|| m.start() < cursor.index && wrapped
|
||||||
{
|
{
|
||||||
Some((m.start(), m.end()))
|
Some((m.start(), m.end()))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -433,7 +434,7 @@ impl EditorTab {
|
||||||
if cursor.line != start_line
|
if cursor.line != start_line
|
||||||
|| m.start() < cursor.index
|
|| m.start() < cursor.index
|
||||||
|| m.start() == cursor.index && current_selection == Selection::None
|
|| m.start() == cursor.index && current_selection == Selection::None
|
||||||
|| m.start() > cursor.index && wrapped == true
|
|| m.start() > cursor.index && wrapped
|
||||||
{
|
{
|
||||||
Some((m.start(), m.end()))
|
Some((m.start(), m.end()))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -317,9 +317,8 @@ where
|
||||||
editor.with_buffer(|buffer| {
|
editor.with_buffer(|buffer| {
|
||||||
let mut layout_lines = 0;
|
let mut layout_lines = 0;
|
||||||
for line in buffer.lines.iter() {
|
for line in buffer.lines.iter() {
|
||||||
match line.layout_opt() {
|
if let Some(layout) = line.layout_opt() {
|
||||||
Some(layout) => layout_lines += layout.len(),
|
layout_lines += layout.len()
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -755,7 +754,7 @@ where
|
||||||
match editor.buffer_ref() {
|
match editor.buffer_ref() {
|
||||||
cosmic_text::BufferRef::Arc(buffer) => {
|
cosmic_text::BufferRef::Arc(buffer) => {
|
||||||
renderer.fill_raw(Raw {
|
renderer.fill_raw(Raw {
|
||||||
buffer: Arc::downgrade(&buffer),
|
buffer: Arc::downgrade(buffer),
|
||||||
position: pos,
|
position: pos,
|
||||||
color: Color::from_rgba(1.0, 1.0, 1.0, 1.0),
|
color: Color::from_rgba(1.0, 1.0, 1.0, 1.0),
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
|
|
@ -1095,8 +1094,7 @@ where
|
||||||
editor.set_redraw(true);
|
editor.set_redraw(true);
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
Named::Tab => {
|
Named::Tab if !modifiers.control() && !modifiers.alt() => {
|
||||||
if !modifiers.control() && !modifiers.alt() {
|
|
||||||
if modifiers.shift() {
|
if modifiers.shift() {
|
||||||
editor.action(Action::Unindent);
|
editor.action(Action::Unindent);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1104,7 +1102,6 @@ where
|
||||||
}
|
}
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
Event::Keyboard(KeyEvent::KeyPressed { text, .. }) if state.is_focused => {
|
Event::Keyboard(KeyEvent::KeyPressed { text, .. }) if state.is_focused => {
|
||||||
|
|
@ -1152,7 +1149,7 @@ where
|
||||||
InputMethodEvent::Commit(text) => {
|
InputMethodEvent::Commit(text) => {
|
||||||
if state.is_focused {
|
if state.is_focused {
|
||||||
editor.start_change();
|
editor.start_change();
|
||||||
editor.insert_string(&text, None);
|
editor.insert_string(text, None);
|
||||||
editor.finish_change();
|
editor.finish_change();
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
|
|
@ -1174,14 +1171,14 @@ where
|
||||||
let y = y_logical * scale_factor;
|
let y = y_logical * scale_factor;
|
||||||
|
|
||||||
// Do this first as the horizontal scrollbar is on top of the buffer
|
// Do this first as the horizontal scrollbar is on top of the buffer
|
||||||
if let Some(scrollbar_h_rect) = state.scrollbar_h_rect.get() {
|
if let Some(scrollbar_h_rect) = state.scrollbar_h_rect.get()
|
||||||
if scrollbar_h_rect.contains(Point::new(x_logical, y_logical)) {
|
&& scrollbar_h_rect.contains(Point::new(x_logical, y_logical))
|
||||||
|
{
|
||||||
state.dragging = Some(Dragging::ScrollbarH {
|
state.dragging = Some(Dragging::ScrollbarH {
|
||||||
start_x: x,
|
start_x: x,
|
||||||
start_scroll: editor.with_buffer(|buffer| buffer.scroll()),
|
start_scroll: editor.with_buffer(|buffer| buffer.scroll()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(state.dragging, Some(Dragging::ScrollbarH { .. })) {
|
if matches!(state.dragging, Some(Dragging::ScrollbarH { .. })) {
|
||||||
// The horizontal scrollbar is on top of the buffer,
|
// The horizontal scrollbar is on top of the buffer,
|
||||||
|
|
@ -1207,7 +1204,7 @@ where
|
||||||
ClickKind::Single
|
ClickKind::Single
|
||||||
};
|
};
|
||||||
let maybe_anchor = if state.modifiers.shift() {
|
let maybe_anchor = if state.modifiers.shift() {
|
||||||
state.shift_anchor.lock().unwrap().clone()
|
*state.shift_anchor.lock().unwrap()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
@ -1388,12 +1385,10 @@ where
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Window(WindowEvent::RedrawRequested(_now)) => {
|
Event::Window(WindowEvent::RedrawRequested(_now)) if state.is_focused => {
|
||||||
if state.is_focused {
|
|
||||||
state.caret_position = editor.cursor_position().unwrap_or(state.caret_position);
|
state.caret_position = editor.cursor_position().unwrap_or(state.caret_position);
|
||||||
shell.request_input_method(&self.input_method(state, scale_factor, layout));
|
shell.request_input_method(&self.input_method(state, scale_factor, layout));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue