feat: refactor to make sure new tabs are added to end of the tab list always

This commit is contained in:
Stevan Freeborn
2026-01-18 08:33:49 -06:00
parent 573724901f
commit 9386fb38fd
+60 -69
View File
@@ -1,6 +1,5 @@
mod file; mod file;
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use iced::padding::bottom; use iced::padding::bottom;
@@ -11,7 +10,6 @@ use iced::{Background, Border, Element, border};
use iced::{Font, Length, Theme}; use iced::{Font, Length, Theme};
use iced::{keyboard, window}; use iced::{keyboard, window};
use rfd::FileDialog; use rfd::FileDialog;
use uuid::Uuid;
const CUSTOM_FONT: Font = Font::with_name("CaskaydiaCove Nerd Font Mono"); const CUSTOM_FONT: Font = Font::with_name("CaskaydiaCove Nerd Font Mono");
const DEFAULT_EDITOR_FONT_SIZE: u32 = 16; const DEFAULT_EDITOR_FONT_SIZE: u32 = 16;
@@ -40,7 +38,6 @@ const MIN_EDITOR_FONT_SIZE: u32 = 12;
// TODO: Opening another window // TODO: Opening another window
struct File { struct File {
id: Uuid,
content: text_editor::Content, content: text_editor::Content,
path: Option<PathBuf>, path: Option<PathBuf>,
} }
@@ -48,7 +45,6 @@ struct File {
impl Default for File { impl Default for File {
fn default() -> Self { fn default() -> Self {
File { File {
id: Uuid::new_v4(),
content: text_editor::Content::new(), content: text_editor::Content::new(),
path: None, path: None,
} }
@@ -63,8 +59,8 @@ impl Default for File {
#[derive(Default)] #[derive(Default)]
struct State { struct State {
files: HashMap<Uuid, File>, files: Vec<File>,
current_file: Uuid, current_file: usize,
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>,
@@ -82,6 +78,7 @@ enum FileAction {
Save, Save,
SaveAs, SaveAs,
Open, Open,
Close(Option<usize>),
} }
impl FileAction { impl FileAction {
@@ -90,6 +87,7 @@ impl FileAction {
FileAction::Save, FileAction::Save,
FileAction::SaveAs, FileAction::SaveAs,
FileAction::Open, FileAction::Open,
FileAction::Close(None),
]; ];
} }
@@ -100,31 +98,32 @@ impl std::fmt::Display for FileAction {
FileAction::Save => write!(f, "Save"), FileAction::Save => write!(f, "Save"),
FileAction::SaveAs => write!(f, "Save as... "), FileAction::SaveAs => write!(f, "Save as... "),
FileAction::Open => write!(f, "Open"), FileAction::Open => write!(f, "Open"),
FileAction::Close(_) => write!(f, "Close"),
} }
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ViewAction { enum ViewAction {
IncreaseFont, Increase,
DecreaseFont, Decrease,
ResetFont, Reset,
} }
impl ViewAction { impl ViewAction {
const ALL: &'static [ViewAction] = &[ const ALL: &'static [ViewAction] = &[
ViewAction::IncreaseFont, ViewAction::Increase,
ViewAction::DecreaseFont, ViewAction::Decrease,
ViewAction::ResetFont, ViewAction::Reset,
]; ];
} }
impl std::fmt::Display for ViewAction { impl std::fmt::Display for ViewAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
ViewAction::DecreaseFont => write!(f, "Decrease font"), ViewAction::Decrease => write!(f, "Decrease font"),
ViewAction::IncreaseFont => write!(f, "Increase font"), ViewAction::Increase => write!(f, "Increase font"),
ViewAction::ResetFont => write!(f, "Reset font"), ViewAction::Reset => write!(f, "Reset font"),
} }
} }
} }
@@ -134,7 +133,7 @@ enum Message {
Edit(text_editor::Action), Edit(text_editor::Action),
FileActionSelected(FileAction), FileActionSelected(FileAction),
ViewActionSelected(ViewAction), ViewActionSelected(ViewAction),
SwitchTab(Uuid), SwitchTab(usize),
} }
fn theme(_state: &State) -> Theme { fn theme(_state: &State) -> Theme {
@@ -144,8 +143,8 @@ fn theme(_state: &State) -> Theme {
fn view(state: &State) -> Element<'_, Message> { fn view(state: &State) -> Element<'_, Message> {
let mut tab_row = row![]; let mut tab_row = row![];
for file in &state.files { for (file_index, file) in state.files.iter().enumerate() {
let file_name = if let Some(p) = &file.1.path { let file_name = if let Some(p) = &file.path {
p.file_name() p.file_name()
.expect("unable to get file name") .expect("unable to get file name")
.to_str() .to_str()
@@ -154,13 +153,15 @@ fn view(state: &State) -> Element<'_, Message> {
"New file" "New file"
}; };
let button_text = text(file_name).wrapping(text::Wrapping::None); let tab_button_text = text(file_name).wrapping(text::Wrapping::None);
let delete_button = button(text("x")).on_press(Message::FileActionSelected(
FileAction::Close(Some(file_index)),
));
let button = button(button_text) let tab_button = button(row![tab_button_text, delete_button])
.style(|theme: &Theme, status| { .style(move |theme: &Theme, status| {
let base = button::primary(theme, status); let base = button::primary(theme, status);
let current_file = state.files.get(&state.current_file).expect("problem"); let is_focused = state.current_file == file_index;
let is_focused = current_file.id == file.0.clone();
let button_background = if is_focused { let button_background = if is_focused {
base.background base.background
} else { } else {
@@ -176,9 +177,9 @@ fn view(state: &State) -> Element<'_, Message> {
..base ..base
} }
}) })
.on_press(Message::SwitchTab(file.0.clone())); .on_press(Message::SwitchTab(file_index));
tab_row = tab_row.push(button); tab_row = tab_row.push(tab_button);
} }
let tabs = scrollable(container(tab_row).padding(bottom(10))).direction( let tabs = scrollable(container(tab_row).padding(bottom(10))).direction(
@@ -201,7 +202,7 @@ fn view(state: &State) -> Element<'_, Message> {
let action_bar = container(row![file_menu, view_menu].spacing(5)); let action_bar = container(row![file_menu, view_menu].spacing(5));
let current_file = state.files.get(&state.current_file).expect("problem"); let current_file = &state.files[state.current_file];
let editor = text_editor(&current_file.content) let editor = text_editor(&current_file.content)
.size(state.editor_font_size) .size(state.editor_font_size)
@@ -227,19 +228,19 @@ fn view(state: &State) -> Element<'_, Message> {
if is_reset_font { if is_reset_font {
return Some(Binding::Custom(Message::ViewActionSelected( return Some(Binding::Custom(Message::ViewActionSelected(
ViewAction::ResetFont, ViewAction::Reset,
))); )));
} }
if is_increase_font { if is_increase_font {
return Some(Binding::Custom(Message::ViewActionSelected( return Some(Binding::Custom(Message::ViewActionSelected(
ViewAction::IncreaseFont, ViewAction::Increase,
))); )));
} }
if is_decrease_font { if is_decrease_font {
return Some(Binding::Custom(Message::ViewActionSelected( return Some(Binding::Custom(Message::ViewActionSelected(
ViewAction::DecreaseFont, ViewAction::Decrease,
))); )));
} }
@@ -280,8 +281,6 @@ fn view(state: &State) -> Element<'_, Message> {
); );
let cursor_text = text(cursor_display_text); let cursor_text = text(cursor_display_text);
let current_file = state.files.get(&state.current_file).expect("problem");
let file_path_display_text = match &current_file.path { let file_path_display_text = match &current_file.path {
Some(path) => path.to_string_lossy().to_string(), Some(path) => path.to_string_lossy().to_string(),
None => String::new(), None => String::new(),
@@ -299,7 +298,7 @@ fn view(state: &State) -> Element<'_, Message> {
fn save_file(path: Option<PathBuf>, text: String) -> Option<PathBuf> { fn save_file(path: Option<PathBuf>, text: String) -> Option<PathBuf> {
let mut save_path = path.clone(); let mut save_path = path.clone();
if path == None { if path.is_none() {
save_path = FileDialog::new().set_directory("/").save_file(); save_path = FileDialog::new().set_directory("/").save_file();
} }
@@ -340,7 +339,7 @@ fn open_file() -> (Option<PathBuf>, String) {
fn update(state: &mut State, message: Message) { fn update(state: &mut State, message: Message) {
match message { match message {
Message::Edit(action) => { Message::Edit(action) => {
let current_file = state.files.get_mut(&state.current_file).expect("problem"); let current_file = &mut state.files[state.current_file];
current_file.content.perform(action); current_file.content.perform(action);
} }
Message::FileActionSelected(action) => { Message::FileActionSelected(action) => {
@@ -348,57 +347,51 @@ fn update(state: &mut State, message: Message) {
match action { match action {
FileAction::SaveAs => { FileAction::SaveAs => {
let current_file = state.files.get_mut(&state.current_file).expect("problem"); let current_file = &mut state.files[state.current_file];
let path = save_file_as(current_file.content.text()); let path = save_file_as(current_file.content.text());
current_file.path = path; current_file.path = path;
} }
FileAction::Open => { FileAction::Open => {
let (path, content) = open_file(); let (path, content) = open_file();
match &path { if let Some(opened_path) = &path {
Some(opened_path) => { for (file_index, file) in state.files.iter_mut().enumerate() {
// TODO: Chat thinks this is stupid if let Some(existing_path) = &file.path
// and I agree. There is probably && opened_path == existing_path
// a way to have some sort of map {
// between id and paths file.content = text_editor::Content::with_text(&content);
for file in state.files.iter_mut() { state.current_file = file_index;
match &file.1.path {
Some(existing_path) => {
if opened_path == existing_path {
file.1.content =
text_editor::Content::with_text(&content);
state.current_file = file.1.id;
return; return;
} }
} }
None => {}
}
}
let opened_file = File { let opened_file = File {
path: path, path,
content: text_editor::Content::with_text(&content), content: text_editor::Content::with_text(&content),
..Default::default()
}; };
let opened_file_id = opened_file.id;
state.files.insert(opened_file.id, opened_file); state.files.push(opened_file);
state.current_file = opened_file_id; state.current_file = state.files.len() - 1;
}
None => {}
} }
} }
FileAction::Save => { FileAction::Save => {
let current_file = state.files.get_mut(&state.current_file).expect("problem"); let current_file = &mut state.files[state.current_file];
let path = save_file(current_file.path.clone(), current_file.content.text()); let path = save_file(current_file.path.clone(), current_file.content.text());
current_file.path = path; current_file.path = path;
} }
FileAction::New => { FileAction::New => {
let default_file = File::default(); let default_file = File::default();
let default_file_id = default_file.id;
state.files.insert(default_file.id, default_file); state.files.push(default_file);
state.current_file = default_file_id; state.current_file = state.files.len() - 1;
}
FileAction::Close(idx) => {
let idx_to_close = match idx {
Some(i) => i,
None => state.current_file,
};
state.files.remove(idx_to_close);
} }
} }
} }
@@ -406,21 +399,21 @@ fn update(state: &mut State, message: Message) {
state.selected_view_action = None; state.selected_view_action = None;
match action { match action {
ViewAction::IncreaseFont => { ViewAction::Increase => {
if state.editor_font_size >= MAX_EDITOR_FONT_SIZE { if state.editor_font_size >= MAX_EDITOR_FONT_SIZE {
return; return;
} }
state.editor_font_size += 2; state.editor_font_size += 2;
} }
ViewAction::DecreaseFont => { ViewAction::Decrease => {
if state.editor_font_size <= MIN_EDITOR_FONT_SIZE { if state.editor_font_size <= MIN_EDITOR_FONT_SIZE {
return; return;
} }
state.editor_font_size -= 2; state.editor_font_size -= 2;
} }
ViewAction::ResetFont => state.editor_font_size = DEFAULT_EDITOR_FONT_SIZE, ViewAction::Reset => state.editor_font_size = DEFAULT_EDITOR_FONT_SIZE,
} }
} }
Message::SwitchTab(file_id) => { Message::SwitchTab(file_id) => {
@@ -430,15 +423,13 @@ fn update(state: &mut State, message: Message) {
} }
fn boot() -> State { fn boot() -> State {
let mut files: HashMap<Uuid, File> = HashMap::new();
let default_file = File::default(); let default_file = File::default();
let default_file_id = default_file.id;
files.insert(default_file.id, default_file); let files = vec![default_file];
State { State {
files: files, files,
current_file: default_file_id, current_file: 0,
editor_font_size: DEFAULT_EDITOR_FONT_SIZE, editor_font_size: DEFAULT_EDITOR_FONT_SIZE,
..Default::default() ..Default::default()
} }