feat: add word wrap toggle functionality

Add the ability to toggle word wrap in the text editor with Alt+Z.
Word wrap can be enabled or disabled, affecting how long lines are
displayed in edit mode.

Changes:
- Add `is_word_wrap_on` state field with default value of false
- Implement `toggle_word_wrap()` and `is_word_wrap_on()` methods
- Add ToggleWordWrap variant to ViewAction enum
- Bind Alt+Z keyboard shortcut to toggle word wrap
- Refactor editor view to support dynamic wrapping behavior
  - Use responsive widget when wrap is enabled to constrain width
  - Apply Wrapping::WordOrGlyph when enabled, Wrapping::None otherwise
- Update editor view signature to accept word wrap state parameter
- Fix indentation inconsistencies in file.rs and state.rs
This commit is contained in:
Stevan Freeborn
2026-02-25 17:17:34 -06:00
parent 7462943c04
commit b382f14a55
7 changed files with 97 additions and 44 deletions
+33 -6
View File
@@ -2,15 +2,28 @@ use iced::{
Background, Element, Length, Theme, Background, Element, Length, Theme,
border::{self}, border::{self},
highlighter, highlighter,
widget::{container, markdown, row, scrollable, text_editor}, widget::{container, markdown, responsive, row, scrollable, text::Wrapping, text_editor},
}; };
use crate::{constants, file::File, message::Message, state::Mode}; use crate::{constants, file::File, message::Message, state::Mode};
pub fn view<'a>(file: &'a File, mode: Mode, font_size: u32) -> Element<'a, Message> { pub fn view<'a>(
file: &'a File,
mode: Mode,
font_size: u32,
is_word_wrap_on: bool,
) -> Element<'a, Message> {
match mode { match mode {
Mode::Edit => { Mode::Edit => {
let editor = text_editor(file.content()) let wrapping = if is_word_wrap_on {
Wrapping::WordOrGlyph
} else {
Wrapping::None
};
let create_editor = move || {
text_editor(file.content())
.wrapping(wrapping)
.highlight( .highlight(
file.extension().unwrap_or("txt"), file.extension().unwrap_or("txt"),
highlighter::Theme::Base16Ocean, highlighter::Theme::Base16Ocean,
@@ -28,9 +41,11 @@ pub fn view<'a>(file: &'a File, mode: Mode, font_size: u32) -> Element<'a, Messa
..base ..base
} }
}) })
.on_action(Message::Edit); .on_action(Message::Edit)
};
scrollable(container(editor).height(Length::Fill)) let create_scrollable = |content: Element<'a, Message>| {
scrollable(content)
.auto_scroll(true) .auto_scroll(true)
.direction(scrollable::Direction::Both { .direction(scrollable::Direction::Both {
vertical: scrollable::Scrollbar::default(), vertical: scrollable::Scrollbar::default(),
@@ -46,14 +61,26 @@ pub fn view<'a>(file: &'a File, mode: Mode, font_size: u32) -> Element<'a, Messa
}) })
.anchor_bottom() .anchor_bottom()
.height(Length::Fill) .height(Length::Fill)
};
if is_word_wrap_on {
responsive(move |size| {
let editor = create_editor();
create_scrollable(container(editor).width(size.width).into()).into()
})
.into() .into()
} else {
let editor = create_editor();
create_scrollable(container(editor).height(Length::Fill).into())
.width(Length::Fill)
.into()
}
} }
Mode::Preview => { Mode::Preview => {
let mut style: markdown::Style = Theme::Dark.into(); let mut style: markdown::Style = Theme::Dark.into();
style.font = constants::CUSTOM_FONT; style.font = constants::CUSTOM_FONT;
let settings = markdown::Settings::with_text_size(font_size, style); let settings = markdown::Settings::with_text_size(font_size, style);
let markdown_preview = markdown::view(file.markdown(), settings).map(Message::LinkClicked); let markdown_preview = markdown::view(file.markdown(), settings).map(Message::LinkClicked);
scrollable(row![markdown_preview].padding(10)) scrollable(row![markdown_preview].padding(10))
+1
View File
@@ -76,6 +76,7 @@ pub fn view_action(state: &mut State, action: ViewAction) -> Task<Message> {
ViewAction::Decrease => state.decrease_font(), ViewAction::Decrease => state.decrease_font(),
ViewAction::Reset => state.reset_font(), ViewAction::Reset => state.reset_font(),
ViewAction::TogglePreview => state.toggle_preview(), ViewAction::TogglePreview => state.toggle_preview(),
ViewAction::ToggleWordWrap => state.toggle_word_wrap(),
} }
Task::none() Task::none()
+5
View File
@@ -65,4 +65,9 @@ pub const ALL: &[Keybinding] = &[
modifiers: Modifiers::CTRL, modifiers: Modifiers::CTRL,
message: Message::ViewActionSelected(ViewAction::Reset), message: Message::ViewActionSelected(ViewAction::Reset),
}, },
Keybinding {
key: "z",
modifiers: Modifiers::ALT,
message: Message::ViewActionSelected(ViewAction::ToggleWordWrap),
},
]; ];
+7 -2
View File
@@ -12,7 +12,7 @@ mod state;
use iced::theme::Palette; use iced::theme::Palette;
use iced::widget::column; use iced::widget::column;
use iced::window::icon; use iced::window::icon;
use iced::{Color, Theme}; use iced::{Color, Length, Theme};
use iced::{Element, Subscription, Task, event}; use iced::{Element, Subscription, Task, event};
use iced::{keyboard, window}; use iced::{keyboard, window};
@@ -84,7 +84,12 @@ fn view(state: &State, _id: iced::window::Id) -> Element<'_, Message> {
column![ column![
components::tabs::view(state.files(), state.current_file_index()), components::tabs::view(state.files(), state.current_file_index()),
components::action_bar::view(state.selected_file_action(), state.selected_view_action()), components::action_bar::view(state.selected_file_action(), state.selected_view_action()),
components::editor::view(current_file, state.mode(), state.font_size()), components::editor::view(
current_file,
state.mode(),
state.font_size(),
state.is_word_wrap_on()
),
components::status_bar::view(current_file), components::status_bar::view(current_file),
] ]
.into() .into()
+3
View File
@@ -39,6 +39,7 @@ pub enum ViewAction {
Decrease, Decrease,
Reset, Reset,
TogglePreview, TogglePreview,
ToggleWordWrap,
} }
impl ViewAction { impl ViewAction {
@@ -47,6 +48,7 @@ impl ViewAction {
ViewAction::Decrease, ViewAction::Decrease,
ViewAction::Reset, ViewAction::Reset,
ViewAction::TogglePreview, ViewAction::TogglePreview,
ViewAction::ToggleWordWrap,
]; ];
} }
@@ -57,6 +59,7 @@ impl Display for ViewAction {
ViewAction::Increase => write!(f, "Increase font"), ViewAction::Increase => write!(f, "Increase font"),
ViewAction::Reset => write!(f, "Reset font"), ViewAction::Reset => write!(f, "Reset font"),
ViewAction::TogglePreview => write!(f, "Toggle preview"), ViewAction::TogglePreview => write!(f, "Toggle preview"),
ViewAction::ToggleWordWrap => write!(f, "Toggle word wrap"),
} }
} }
} }
+13 -1
View File
@@ -22,6 +22,7 @@ pub struct State {
editor_font_size: u32, editor_font_size: u32,
selected_file_action: Option<FileAction>, selected_file_action: Option<FileAction>,
selected_view_action: Option<ViewAction>, selected_view_action: Option<ViewAction>,
is_word_wrap_on: bool,
} }
impl State { impl State {
@@ -33,6 +34,7 @@ impl State {
files: vec![default_file], files: vec![default_file],
current_file: 0, current_file: 0,
editor_font_size: constants::DEFAULT_EDITOR_FONT_SIZE, editor_font_size: constants::DEFAULT_EDITOR_FONT_SIZE,
is_word_wrap_on: false,
..Default::default() ..Default::default()
} }
} }
@@ -42,7 +44,9 @@ impl State {
} }
pub fn apply_edit(&mut self, action: text_editor::Action) { pub fn apply_edit(&mut self, action: text_editor::Action) {
self.files[self.current_file].content_mut().perform(action.clone()); self.files[self.current_file]
.content_mut()
.perform(action.clone());
if matches!(action, text_editor::Action::Edit(_)) { if matches!(action, text_editor::Action::Edit(_)) {
self.files[self.current_file].set_needs_saving(true); self.files[self.current_file].set_needs_saving(true);
@@ -129,6 +133,14 @@ impl State {
} }
} }
pub fn toggle_word_wrap(&mut self) {
self.is_word_wrap_on = !self.is_word_wrap_on;
}
pub fn is_word_wrap_on(&self) -> bool {
self.is_word_wrap_on
}
pub fn files(&self) -> &[file::File] { pub fn files(&self) -> &[file::File] {
&self.files &self.files
} }