Skip to content

text_editor::Edit::Paste doesn't trigger syntax highlight like other Edit variants in case of custom highlight rules #2946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
4 tasks done
Atreyagaurav opened this issue May 16, 2025 · 4 comments · May be fixed by #2947
Open
4 tasks done
Labels
bug Something isn't working

Comments

@Atreyagaurav
Copy link

Atreyagaurav commented May 16, 2025

Is your issue REALLY a bug?

  • My issue is indeed a bug!
  • I am not crazy! I will not fill out this form just to ask a question or request a feature. Pinky promise.

Is there an existing issue for this?

  • I have searched the existing issues.

Is this issue related to iced?

  • My hardware is compatible and my graphics drivers are up-to-date.

What happened?

When I copy and paste multiline strings in TextEditor only the last line gets syntax highlight. I have a custom syntax highlight rules, I cannot see any way to trigger the highlight, or to call the change_line programmatically while changing the editor Content.

You can see in the picture below that the pasted block only has the last line highlighted. When I go and edit something above them, then it gets highlighted.

Image

I didn't test it in master because I didn't find any issues related to this, and it seems to work fine with the default language based highlighter. (And I couldn't compile it in master because the iced_core::Color::new) seems to be private now, and other changes)

Here is a minimal reproducible example, you can try typing some texts and then copy multiple lines:

[package]
name = "ice-issue"
version = "0.1.0"
edition = "2024"

[dependencies]
iced = { version = "0.13.1", features = ["highlighter"] }
iced_core = "0.13.2"

src/main.rs

use core::ops::Range;
use iced::widget::text_editor;
use iced::Color;
use iced::{Element, Fill, Font};
use iced_core::text::highlighter::{Format, Highlighter};

fn main() -> iced::Result {
    iced::application("temp Editor", Editor::update, Editor::view).run()
}

#[derive(Default)]
struct Editor {
    content: text_editor::Content,
}

#[derive(Clone, Debug)]
enum Message {
    Editor(text_editor::Action),
}

impl Editor {
    fn update(&mut self, message: Message) {
        match message {
            Message::Editor(a) => self.content.perform(a),
        }
    }

    fn view(&self) -> Element<'_, Message> {
        text_editor(&self.content)
            .height(Fill)
            .on_action(Message::Editor)
            .font(Font::MONOSPACE)
            .highlight_with::<MyHighlighter>((), Highlight::to_format)
            .into()
    }
}

enum Highlight {
    First,
    Second,
    None,
}

impl Highlight {
    fn get(i: usize) -> Self {
        match i {
            0 => Self::First,
            1 => Self::Second,
            _ => Self::None,
        }
    }

    fn to_format(&self, _theme: &iced::Theme) -> Format<Font> {
        let color = match self {
            Self::First => Some(Color::new(0.0, 0.0, 0.1, 1.0)),
            Self::Second => Some(Color::new(1.0, 0.0, 0.0, 1.0)),
            Self::None => None,
        };
        Format { color, font: None }
    }
}

fn highlight_line(line: &str) -> Vec<(Range<usize>, Highlight)> {
    let mut offset = 0;
    line.split(" ")
        .enumerate()
        .map(|(i, l)| {
            let start = offset;
            offset += l.len() + 1;
            (start..offset, Highlight::get(i))
        })
        .collect()
}

struct MyHighlighter {
    curr_line: usize,
}

impl Highlighter for MyHighlighter {
    type Settings = ();
    type Highlight = Highlight;
    type Iterator<'a> = Box<dyn Iterator<Item = (Range<usize>, Self::Highlight)>>;
    fn new(_: &Self::Settings) -> Self {
        Self { curr_line: 0 }
    }
    fn update(&mut self, _: &Self::Settings) {
        self.change_line(0);
    }

    fn change_line(&mut self, line: usize) {
        self.curr_line = line;
    }
    fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> {
        Box::new(highlight_line(line).into_iter())
    }
    fn current_line(&self) -> usize {
        self.curr_line
    }
}

What is the expected behavior?

The syntax highlight is triggered from the first line of the pasted string, instead of from the last line.

Version

0.13.1

Operating System

Arch Linux

Do you have any log output?

No

@Atreyagaurav Atreyagaurav added the bug Something isn't working label May 16, 2025
@pml68
Copy link
Contributor

pml68 commented May 16, 2025

You really should try it on master even if there isn't a related issue. iced::Color::new is private, but iced::Color::from_rgba is the exact same API-wise.

@Atreyagaurav
Copy link
Author

Atreyagaurav commented May 16, 2025

The problem in the code is probably here:

Edit::Insert(c) => {
editor.action(
font_system.raw(),
cosmic_text::Action::Insert(c),
);
}
Edit::Paste(text) => {
editor.insert_string(&text, None);
}
Edit::Indent => {
editor.action(
font_system.raw(),
cosmic_text::Action::Indent,
);
}

See how the Paste is running different code than the rest. And the insert_string method doesn't reset the draw cache, action.

The fix should be as easy as: [Edit: this doesn't seem to be solve the problem... but doing something here should work..] ,

                    Edit::Paste(text) => {
                        editor.insert_string(&text, None);
+                        editor.set_redraw(true);
                    }

Maybe more complicated if you want to test things before calling redraw.

But for completeness, I tested it in master, the README seems to be out of date, that's why I couldn't run it following that example.

This no longer works.

fn main() -> iced::Result {
    iced::run("A cool counter", Counter::update, Counter::view)
}

Because of this:

iced/src/lib.rs

Lines 693 to 697 in 5de7bc8

pub fn run<State, Message, Theme, Renderer>(
update: impl application::Update<State, Message> + 'static,
view: impl for<'a> application::View<'a, State, Message, Theme, Renderer>
+ 'static,
) -> Result

Anyway, here is the code for master:

[package]
name = "ice-issue"
version = "0.1.0"
edition = "2024"

[dependencies]
iced = { git = "https://github.com/iced-rs/iced.git" , features = ["highlighter"] }
iced_core = {git = "https://github.com/iced-rs/iced.git"}
use core::ops::Range;
use iced::widget::text_editor;
use iced::Color;
use iced::{Element, Fill, Font};
use iced_core::text::highlighter::{Format, Highlighter};

fn main() -> iced::Result {
    iced::run(Editor::update, Editor::view)
}

#[derive(Default)]
struct Editor {
    content: text_editor::Content,
}

#[derive(Clone, Debug)]
enum Message {
    Editor(text_editor::Action),
}

impl Editor {
    fn update(&mut self, message: Message) {
        match message {
            Message::Editor(a) => self.content.perform(a),
        }
    }

    fn view(&self) -> Element<'_, Message> {
        text_editor(&self.content)
            .height(Fill)
            .on_action(Message::Editor)
            .font(Font::MONOSPACE)
            .highlight_with::<MyHighlighter>((), Highlight::to_format)
            .into()
    }
}

enum Highlight {
    First,
    Second,
    None,
}

impl Highlight {
    fn get(i: usize) -> Self {
        match i {
            0 => Self::First,
            1 => Self::Second,
            _ => Self::None,
        }
    }

    fn to_format(&self, _theme: &iced::Theme) -> Format<Font> {
        let color = match self {
            Self::First => Some(Color::from_rgb(0.0, 0.0, 0.1)),
            Self::Second => Some(Color::from_rgb(1.0, 0.0, 0.0)),
            Self::None => None,
        };
        Format { color, font: None }
    }
}

fn highlight_line(line: &str) -> Vec<(Range<usize>, Highlight)> {
    let mut offset = 0;
    line.split(" ")
        .enumerate()
        .map(|(i, l)| {
            let start = offset;
            offset += l.len() + 1;
            (start..offset, Highlight::get(i))
        })
        .collect()
}

struct MyHighlighter {
    curr_line: usize,
}

impl Highlighter for MyHighlighter {
    type Settings = ();
    type Highlight = Highlight;
    type Iterator<'a> = Box<dyn Iterator<Item = (Range<usize>, Self::Highlight)>>;
    fn new(_: &Self::Settings) -> Self {
        Self { curr_line: 0 }
    }
    fn update(&mut self, _: &Self::Settings) {
        self.change_line(0);
    }

    fn change_line(&mut self, line: usize) {
        self.curr_line = line;
    }
    fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> {
        Box::new(highlight_line(line).into_iter())
    }
    fn current_line(&self) -> usize {
        self.curr_line
    }
}

Atreyagaurav added a commit to Atreyagaurav/iced that referenced this issue May 16, 2025
@Atreyagaurav Atreyagaurav linked a pull request May 16, 2025 that will close this issue
@pml68
Copy link
Contributor

pml68 commented May 16, 2025

the README seems to be out of date

Thanks for the notice!

Atreyagaurav added a commit to Atreyagaurav/iced that referenced this issue May 16, 2025
Atreyagaurav added a commit to Atreyagaurav/iced that referenced this issue May 16, 2025
@Atreyagaurav
Copy link
Author

Seems to be fixed on my local run now. I didn't realize the top changed line was overwritten at the end based on the cursor position. I think the assumption was for single line edits only.

Atreyagaurav added a commit to Atreyagaurav/iced that referenced this issue May 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants