diff --git a/Cargo.lock b/Cargo.lock
index 751dd12..cdb2db1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1503,6 +1503,7 @@ dependencies = [
  "iced_core",
  "log",
  "rustc-hash 2.1.1",
+ "tokio",
  "wasm-bindgen-futures",
  "wasmtimer",
 ]
@@ -3656,6 +3657,15 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
+[[package]]
+name = "tokio"
+version = "1.49.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
+dependencies = [
+ "pin-project-lite",
+]
+
 [[package]]
 name = "toml_datetime"
 version = "0.7.5+spec-1.1.0"
@@ -4849,6 +4859,7 @@ version = "0.1.0"
 dependencies = [
  "iced",
  "rfd",
+ "tokio",
  "uuid",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index 8daeef9..14fe584 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
 edition = "2024"
 
 [dependencies]
-iced = { version = "0.14.0", features = ["image", "markdown", "highlighter"] }
+iced = { version = "0.14.0", features = ["image", "markdown", "highlighter", "tokio"] }
 rfd = "0.17.1"
+tokio = { version ="1.49.0", features = ["fs"] }
 uuid = { version = "1.19.0", features = ["v4"] }
diff --git a/src/components/action_bar.rs b/src/components/action_bar.rs
new file mode 100644
index 0000000..4dd0ac4
--- /dev/null
+++ b/src/components/action_bar.rs
@@ -0,0 +1,29 @@
+use iced::{Element, Length, widget::{container, pick_list, row}};
+
+use crate::message::{FileAction, Message, ViewAction};
+
+pub fn view(
+    selected_file_action: Option<FileAction>,
+    selected_view_action: Option<ViewAction>,
+) -> Element<'static, Message> {
+    
+    let file_menu = pick_list(
+        FileAction::ALL,
+        selected_file_action,
+        Message::FileActionSelected,
+    )
+    .placeholder("File");
+
+    let view_menu = pick_list(
+        ViewAction::ALL,
+        selected_view_action,
+        Message::ViewActionSelected,
+    )
+    .placeholder("View");
+
+    container(
+        row![file_menu, view_menu].spacing(10)
+    )
+    .width(Length::Fill)
+    .into()
+}
diff --git a/src/components/editor.rs b/src/components/editor.rs
new file mode 100644
index 0000000..d2a52fd
--- /dev/null
+++ b/src/components/editor.rs
@@ -0,0 +1,32 @@
+use iced::{
+    Element, Length, Theme,
+    widget::{container, markdown, row, text_editor},
+};
+
+use crate::{constants, file::File, message::Message, state::Mode};
+
+pub fn view<'a>(file: &'a File, mode: Mode, font_size: u32) -> Element<'a, Message> {
+    match mode {
+        Mode::Edit => {
+            let editor = text_editor(file.content())
+                .size(font_size)
+                .height(Length::Fill)
+                .on_action(Message::Edit);
+
+            container(row![editor]).height(Length::Fill).into()
+        }
+        Mode::Preview => {
+            let mut style: markdown::Style = Theme::Dark.into();
+            style.font = constants::CUSTOM_FONT;
+
+            let settings = markdown::Settings::with_text_size(font_size, style);
+
+            let markdown_preview =
+                markdown::view(file.markdown(), settings).map(Message::LinkClicked);
+
+            container(row![markdown_preview])
+                .height(Length::Fill)
+                .into()
+        }
+    }
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
new file mode 100644
index 0000000..6e9ede3
--- /dev/null
+++ b/src/components/mod.rs
@@ -0,0 +1,4 @@
+pub mod tabs;
+pub mod editor;
+pub mod status_bar;
+pub mod action_bar;
diff --git a/src/components/status_bar.rs b/src/components/status_bar.rs
new file mode 100644
index 0000000..a7cbeed
--- /dev/null
+++ b/src/components/status_bar.rs
@@ -0,0 +1,16 @@
+use iced::widget::{container, row, text};
+use iced::{Element, Length};
+use crate::file::File;
+
+pub fn view(file: &File) -> Element<'_, crate::Message> {
+    let path_text = text(file.path_summary());
+    let cursor_text = text(file.position_summary());
+
+    container(
+        row![cursor_text, path_text]
+            .spacing(20)
+    )
+    .width(Length::Fill)
+    .padding(5)
+    .into()
+}
diff --git a/src/components/tabs.rs b/src/components/tabs.rs
new file mode 100644
index 0000000..842cf54
--- /dev/null
+++ b/src/components/tabs.rs
@@ -0,0 +1,41 @@
+use crate::file::File;
+use crate::message::{FileAction, Message};
+use iced::{Background, Border, Theme, border};
+use iced::{
+    Element,
+    widget::{button, container, row, scrollable, text},
+};
+
+pub fn view<'a>(files: &'a [File], active_index: usize) -> Element<'a, Message> {
+    let tabs = files.iter().enumerate().map(|(index, file)| {
+        let is_focused = index == active_index;
+
+        let label = text(file.display_name());
+        let close_btn =
+            button(text("x")).on_press(Message::FileActionSelected(FileAction::Close(Some(index))));
+
+        button(row![label, close_btn].spacing(5))
+            .on_press(Message::SwitchTab(index))
+            .padding(5)
+            .style(move |theme: &Theme, status| {
+                let base = button::primary(theme, status);
+                let button_background = if is_focused {
+                    base.background
+                } else {
+                    Some(Background::Color(theme.palette().background))
+                };
+
+                button::Style {
+                    background: button_background,
+                    border: Border {
+                        radius: border::radius(0).top_left(10).top_right(10),
+                        ..base.border
+                    },
+                    ..base
+                }
+            })
+            .into()
+    });
+
+    scrollable(container(row(tabs).spacing(2))).into()
+}
diff --git a/src/constants.rs b/src/constants.rs
new file mode 100644
index 0000000..4e4acb9
--- /dev/null
+++ b/src/constants.rs
@@ -0,0 +1,8 @@
+use iced::Font;
+
+pub const CUSTOM_FONT_BYTES: &[u8] = include_bytes!("./fonts/CaskaydiaCoveNFM-Regular.ttf");
+pub const CUSTOM_FONT: Font = Font::with_name("CaskaydiaCove Nerd Font Mono");
+pub const DEFAULT_EDITOR_FONT_SIZE: u32 = 16;
+pub const MAX_EDITOR_FONT_SIZE: u32 = 80;
+pub const MIN_EDITOR_FONT_SIZE: u32 = 12;
+pub const ICON_BYTES: &[u8] = include_bytes!("./images/icon.ico");
diff --git a/src/file.rs b/src/file.rs
index 105303d..fcf1ea4 100644
--- a/src/file.rs
+++ b/src/file.rs
@@ -1,55 +1,80 @@
-use std::{
-    fs::{read_to_string, write},
-    path::PathBuf,
-};
+use std::path::PathBuf;
 
-pub fn save_file_to_disk(path: PathBuf, text: String) {
-    let save_result = write(path, text);
+use iced::widget::{markdown, text_editor};
 
-    match save_result {
-        Ok(_) => {},
-        Err(err) => eprintln!("Error: {}", err),
+pub struct File {
+    content: text_editor::Content,
+    path: Option<PathBuf>,
+    markdown: Vec<markdown::Item>,
+}
+
+impl Default for File {
+    fn default() -> Self {
+        File {
+            content: text_editor::Content::new(),
+            path: None,
+            markdown: Vec::new(),
+        }
     }
 }
 
-pub fn load_file_from_disk(path: PathBuf) -> String {
-    let read_result = read_to_string(path);
+impl File {
+    pub fn from(content: &str, path: Option<PathBuf>) -> Self {
+        let text_editor_content = text_editor::Content::with_text(content);
+        let markdown = markdown::parse(content).collect();
 
-    match read_result {
-        Ok(contents) => contents,
-        Err(_) => String::new(),
+        File {
+            content: text_editor_content,
+            path,
+            markdown,
+        }
     }
-}
 
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::fs::{self, remove_file};
+    pub fn content(&self) -> &text_editor::Content {
+        &self.content
+    }
 
-    #[test]
-    fn test_save_file_to_disk() {
-        let test_path = PathBuf::from("test_file.txt");
-        let test_content = String::from("Hello, Chat!");
+    pub fn content_mut(&mut self) -> &mut text_editor::Content {
+        &mut self.content
+    }
 
-        save_file_to_disk(test_path.clone(), test_content.clone());
+    pub fn set_content(&mut self, content: &str) {
+        self.content = text_editor::Content::with_text(content);
+    }
 
-        let saved_content = fs::read_to_string(&test_path).unwrap();
+    pub fn markdown(&self) -> Vec<&markdown::Item> {
+        self.markdown.iter().collect()
+    }
 
-        assert_eq!(saved_content, test_content);
+    pub fn update_markdown(&mut self) {
+        self.markdown = markdown::parse(&self.content.text()).collect();
+    }
 
-        let _ = remove_file(test_path);
+    pub fn path(&self) -> Option<&PathBuf> {
+        self.path.as_ref()
     }
 
-    #[test]
-    fn test_load_file_from_disk() {
-        let test_path = PathBuf::from("test_file.txt");
-        let test_content = String::from("Hello, Chat!");
-        let _ = fs::write(test_path.clone(), test_content.clone());
+    pub fn set_path(&mut self, path: Option<PathBuf>) {
+        self.path = path;
+    }
 
-        let saved_content = load_file_from_disk(test_path.clone());
+    pub fn display_name(&self) -> &str {
+        self.path
+            .as_deref()
+            .and_then(|p| p.file_name())
+            .and_then(|n| n.to_str())
+            .unwrap_or("New file")
+    }
 
-        assert_eq!(saved_content, test_content);
+    pub fn position_summary(&self) -> String {
+        let pos = self.content.cursor().position;
+        format!("Ln {}, Col {}", pos.line, pos.column)
+    }
 
-        let _ = remove_file(test_path);
+    pub fn path_summary(&self) -> String {
+        self.path
+            .as_deref()
+            .map(|p| p.to_string_lossy().to_string())
+            .unwrap_or_default()
     }
 }
diff --git a/src/handler.rs b/src/handler.rs
new file mode 100644
index 0000000..110006d
--- /dev/null
+++ b/src/handler.rs
@@ -0,0 +1,81 @@
+use std::path::PathBuf;
+
+use crate::io;
+use crate::message::{FileAction, ViewAction};
+use crate::{Message, state::State};
+use iced::Task;
+use iced::widget::text_editor;
+
+pub fn edit(state: &mut State, action: text_editor::Action) -> Task<Message> {
+    state.apply_edit(action);
+    Task::none()
+}
+
+pub fn switch_tab(state: &mut State, index: usize) -> Task<Message> {
+    state.switch_tab(index);
+    Task::none()
+}
+
+pub fn link_clicked(url: String) -> Task<Message> {
+    println!("Opening link: {}", url);
+    Task::none()
+}
+
+pub fn file_action(state: &mut State, action: FileAction) -> Task<Message> {
+    match action {
+        FileAction::New => {
+            state.new_file();
+            Task::none()
+        }
+        FileAction::Close(index) => {
+            if state.close_file(index) {
+                iced::exit()
+            } else {
+                Task::none()
+            }
+        }
+        FileAction::Open => Task::perform(io::open_file(), Message::FileOpened),
+        FileAction::Save => {
+            let (current_path, content) = state.active_file_data();
+            let path = current_path.cloned();
+            Task::perform(io::save_file(path, content), Message::FileSaved)
+        }
+        FileAction::SaveAs => {
+            let (_, content) = state.active_file_data();
+            Task::perform(io::save_file(None, content), Message::FileSaved)
+        }
+    }
+}
+
+pub fn opened_file(state: &mut State, result: Result<(PathBuf, String), String>) -> Task<Message> {
+    match result {
+        Ok((path, content)) => {
+            state.open_file(path, content);
+        }
+        Err(_error) => {}
+    };
+
+    Task::none()
+}
+
+pub fn saved_file(state: &mut State, result: Result<PathBuf, String>) -> Task<Message> {
+    match result {
+        Ok(path) => {
+            state.set_active_file_path(path);
+        }
+        Err(_error) => {}
+    };
+
+    Task::none()
+}
+
+pub fn view_action(state: &mut State, action: ViewAction) -> Task<Message> {
+    match action {
+        ViewAction::Increase => state.increase_font(),
+        ViewAction::Decrease => state.decrease_font(),
+        ViewAction::Reset => state.reset_font(),
+        ViewAction::TogglePreview => state.toggle_preview(),
+    }
+
+    Task::none()
+}
diff --git a/src/io.rs b/src/io.rs
new file mode 100644
index 0000000..c36cf8d
--- /dev/null
+++ b/src/io.rs
@@ -0,0 +1,45 @@
+use std::path::PathBuf;
+
+use rfd::AsyncFileDialog;
+use tokio::fs;
+
+pub async fn open_file() -> Result<(PathBuf, String), String> {
+    let handle_result = AsyncFileDialog::new()
+        .set_directory("/")
+        .pick_file()
+        .await
+        .ok_or(String::from("Dialog cancelled"));
+
+    match handle_result {
+        Ok(handle) => {
+            let path = handle.path().to_owned();
+            let content_result = fs::read_to_string(&path).await.map_err(|e| e.to_string());
+
+            match content_result {
+                Ok(content) => Ok((path, content)),
+                Err(e) => Err(e),
+            }
+        }
+        Err(e) => Err(e),
+    }
+}
+
+pub async fn save_file(path: Option<PathBuf>, text: String) -> Result<PathBuf, String> {
+    let save_path = match path {
+        Some(p) => p,
+        None => AsyncFileDialog::new()
+            .set_directory("/")
+            .save_file()
+            .await
+            .ok_or(String::from("Dialog cancelled"))?
+            .path()
+            .to_owned(),
+    };
+
+    let save_result = fs::write(&save_path, text).await.map_err(|e| e.to_string());
+
+    match save_result {
+        Ok(_) => Ok(save_path),
+        Err(err) => Err(err),
+    }
+}
diff --git a/src/key_bindings.rs b/src/key_bindings.rs
new file mode 100644
index 0000000..f1eb0ab
--- /dev/null
+++ b/src/key_bindings.rs
@@ -0,0 +1,68 @@
+use iced::keyboard::{self, Key, Modifiers};
+
+use crate::message::{FileAction, Message, ViewAction};
+
+pub struct Keybinding {
+    key: &'static str,
+    modifiers: Modifiers,
+    message: Message,
+}
+
+impl Keybinding {
+    pub fn should_handle(&self, key_pressed: &Key, modifiers: &Modifiers) -> bool {
+        let key = keyboard::Key::Character(self.key.into());
+        modifiers.contains(self.modifiers) && key_pressed == &key
+    }
+
+    pub fn message(&self) -> Message {
+        self.message.clone()
+    }
+}
+
+pub const ALL: &[Keybinding] = &[
+    Keybinding {
+        key: "o",
+        modifiers: Modifiers::CTRL,
+        message: Message::FileActionSelected(FileAction::Open),
+    },
+    Keybinding {
+        key: "n",
+        modifiers: Modifiers::CTRL,
+        message: Message::FileActionSelected(FileAction::New),
+    },
+    Keybinding {
+        key: "s",
+        modifiers: Modifiers::CTRL.union(Modifiers::SHIFT),
+        message: Message::FileActionSelected(FileAction::SaveAs),
+    },
+    Keybinding {
+        key: "s",
+        modifiers: Modifiers::CTRL,
+        message: Message::FileActionSelected(FileAction::Save),
+    },
+    Keybinding {
+        key: "w",
+        modifiers: Modifiers::CTRL,
+        message: Message::FileActionSelected(FileAction::Close(None)),
+    },
+    Keybinding {
+        key: "p",
+        modifiers: Modifiers::CTRL,
+        message: Message::ViewActionSelected(ViewAction::TogglePreview),
+    },
+    Keybinding {
+        key: "=",
+        modifiers: Modifiers::CTRL,
+        message: Message::ViewActionSelected(ViewAction::Increase),
+    },
+    Keybinding {
+        key: "-",
+        modifiers: Modifiers::CTRL,
+        message: Message::ViewActionSelected(ViewAction::Decrease),
+    },
+    Keybinding {
+        key: "0",
+        modifiers: Modifiers::CTRL,
+        message: Message::ViewActionSelected(ViewAction::Reset),
+    },
+];
diff --git a/src/main.rs b/src/main.rs
index 18a6c3c..8e0b29b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,468 +1,75 @@
+mod components;
+mod constants;
 mod file;
-
-use std::path::PathBuf;
-
-use iced::keyboard::{Key, Modifiers};
-use iced::padding::bottom;
-use iced::widget::{
-    button, column, container, markdown, pick_list, row, scrollable, text, text_editor,
-};
+mod handler;
+mod io;
+mod key_bindings;
+mod message;
+mod state;
+
+use iced::Theme;
+use iced::widget::column;
 use iced::window::icon;
-use iced::{Background, Border, Element, Subscription, Task, border, event};
-use iced::{Font, Length, Theme};
+use iced::{Element, Subscription, Task, event};
 use iced::{keyboard, window};
-use rfd::FileDialog;
-
-const CUSTOM_FONT: Font = Font::with_name("CaskaydiaCove Nerd Font Mono");
-const DEFAULT_EDITOR_FONT_SIZE: u32 = 16;
-const MAX_EDITOR_FONT_SIZE: u32 = 80;
-const MIN_EDITOR_FONT_SIZE: u32 = 12;
-
-struct File {
-    content: text_editor::Content,
-    path: Option<PathBuf>,
-    markdown: Vec<markdown::Item>,
-}
-
-impl Default for File {
-    fn default() -> Self {
-        File {
-            content: text_editor::Content::new(),
-            path: None,
-            markdown: Vec::new(),
-        }
-    }
-}
-
-#[derive(Default)]
-enum Mode {
-    #[default]
-    Edit,
-    Preview,
-}
-
-#[derive(Default)]
-struct State {
-    mode: Mode,
-    files: Vec<File>,
-    current_file: usize,
-    editor_font_size: u32,
-    selected_file_action: Option<FileAction>,
-    selected_view_action: Option<ViewAction>,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum FileAction {
-    New,
-    Save,
-    SaveAs,
-    Open,
-    Close(Option<usize>),
-}
-
-impl FileAction {
-    const ALL: &'static [FileAction] = &[
-        FileAction::New,
-        FileAction::Save,
-        FileAction::SaveAs,
-        FileAction::Open,
-        FileAction::Close(None),
-    ];
-}
-
-impl std::fmt::Display for FileAction {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            FileAction::New => write!(f, "New file"),
-            FileAction::Save => write!(f, "Save"),
-            FileAction::SaveAs => write!(f, "Save as... "),
-            FileAction::Open => write!(f, "Open"),
-            FileAction::Close(_) => write!(f, "Close"),
-        }
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum ViewAction {
-    Increase,
-    Decrease,
-    Reset,
-    TogglePreview,
-}
-
-impl ViewAction {
-    const ALL: &'static [ViewAction] = &[
-        ViewAction::Increase,
-        ViewAction::Decrease,
-        ViewAction::Reset,
-        ViewAction::TogglePreview,
-    ];
-}
-
-impl std::fmt::Display for ViewAction {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            ViewAction::Decrease => write!(f, "Decrease font"),
-            ViewAction::Increase => write!(f, "Increase font"),
-            ViewAction::Reset => write!(f, "Reset font"),
-            ViewAction::TogglePreview => write!(f, "Toggle preview"),
-        }
-    }
-}
-
-#[derive(Debug, Clone)]
-enum Message {
-    Edit(text_editor::Action),
-    FileActionSelected(FileAction),
-    ViewActionSelected(ViewAction),
-    SwitchTab(usize),
-    LinkClicked(String),
-}
-
-// TODO: Can we at least make
-// theme configurable
-// and then maybe even define
-// complete custom and/or use
-// existing as starter
-fn theme(_state: &State) -> Theme {
-    Theme::Dark
-}
-
-fn view(state: &State) -> Element<'_, Message> {
-    let mut tab_row = row![];
 
-    for (file_index, file) in state.files.iter().enumerate() {
-        let file_name = if let Some(p) = &file.path {
-            p.file_name()
-                .expect("unable to get file name")
-                .to_str()
-                .expect("unable to get file name")
-        } else {
-            "New file"
-        };
+use crate::message::Message;
+use crate::state::State;
 
-        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 tab_button = button(row![tab_button_text, delete_button])
-            .style(move |theme: &Theme, status| {
-                let base = button::primary(theme, status);
-                let is_focused = state.current_file == file_index;
-                let button_background = if is_focused {
-                    base.background
-                } else {
-                    Some(Background::Color(theme.palette().background))
-                };
-
-                button::Style {
-                    background: button_background,
-                    border: Border {
-                        radius: border::radius(0).top_left(10).top_right(10),
-                        ..base.border
-                    },
-                    ..base
-                }
-            })
-            .on_press(Message::SwitchTab(file_index));
-
-        tab_row = tab_row.push(tab_button);
-    }
-
-    let tabs = scrollable(container(tab_row).padding(bottom(10))).direction(
-        scrollable::Direction::Horizontal(scrollable::Scrollbar::new()),
-    );
-
-    let file_menu = pick_list(
-        FileAction::ALL,
-        state.selected_file_action,
-        Message::FileActionSelected,
-    )
-    .placeholder("File");
-
-    let view_menu = pick_list(
-        ViewAction::ALL,
-        state.selected_view_action,
-        Message::ViewActionSelected,
-    )
-    .placeholder("View");
-
-    let action_bar = container(row![file_menu, view_menu].spacing(5));
-
-    let current_file = &state.files[state.current_file];
-
-    let editor = text_editor(&current_file.content)
-        .size(state.editor_font_size)
-        .height(Length::Fill)
-        .on_action(Message::Edit);
-
-    let mut markdown_styles: markdown::Style = Theme::Dark.into();
-    markdown_styles.font = CUSTOM_FONT;
-
-    let markdown_settings =
-        markdown::Settings::with_text_size(state.editor_font_size, markdown_styles);
-
-    let markdown_preview =
-        markdown::view(&current_file.markdown, markdown_settings).map(Message::LinkClicked);
-
-    let preview_container = container(row![markdown_preview]).height(Length::Fill);
-    let editor_container = container(row![editor]).height(Length::Fill);
-
-    let cursor_position = current_file.content.cursor().position;
-
-    let cursor_display_text = format!(
-        "Ln {}, Col {}",
-        cursor_position.line, cursor_position.column
-    );
-    let cursor_text = text(cursor_display_text);
-
-    let file_path_display_text = match &current_file.path {
-        Some(path) => path.to_string_lossy().to_string(),
-        None => String::new(),
-    };
-
-    let file_path_text = text(file_path_display_text);
-
-    let status_bar = container(row![file_path_text, cursor_text].spacing(10));
-
-    let main = match state.mode {
-        Mode::Edit => editor_container,
-        Mode::Preview => preview_container,
-    };
-
-    container(column![tabs, action_bar, main, status_bar])
-        .padding(10)
-        .into()
-}
-
-fn save_file(path: Option<PathBuf>, text: String) -> Option<PathBuf> {
-    let mut save_path = path.clone();
-
-    if path.is_none() {
-        save_path = FileDialog::new().set_directory("/").save_file();
-    }
-
-    match save_path {
-        Some(p) => {
-            let sp = p.clone();
-            file::save_file_to_disk(sp, text);
-            Some(p)
-        }
-        None => None,
-    }
-}
-
-fn save_file_as(text: String) -> Option<PathBuf> {
-    let files = FileDialog::new().set_directory("/").save_file();
-
-    match files {
-        Some(path) => {
-            file::save_file_to_disk(path.clone(), text);
-            Some(path)
-        }
-        None => None,
-    }
+pub fn main() -> iced::Result {
+    iced::application(boot, update, view)
+        .subscription(subscription)
+        .font(constants::CUSTOM_FONT_BYTES)
+        .theme(theme)
+        .settings(iced::Settings {
+            default_font: constants::CUSTOM_FONT,
+            ..Default::default()
+        })
+        .window(window::Settings {
+            icon: Some(
+                icon::from_file_data(constants::ICON_BYTES, None).expect("Failed to load icon"),
+            ),
+            ..window::Settings::default()
+        })
+        .run()
 }
 
-fn open_file() -> (Option<PathBuf>, String) {
-    // TODO: Allow opening multiple files
-    let file = FileDialog::new().set_directory("/").pick_file();
-
-    match file {
-        Some(path) => {
-            let content = file::load_file_from_disk(path.clone());
-            (Some(path), content)
-        }
-        None => (None, String::new()),
-    }
+fn boot() -> State {
+    state::State::new()
 }
 
 fn update(state: &mut State, message: Message) -> Task<Message> {
     match message {
-        Message::Edit(action) => {
-            let current_file = &mut state.files[state.current_file];
-            current_file.content.perform(action);
-            current_file.markdown = markdown::parse(&current_file.content.text()).collect();
-        }
-        Message::FileActionSelected(action) => {
-            state.selected_file_action = None;
-
-            match action {
-                FileAction::SaveAs => {
-                    println!("We are here!");
-                    let current_file = &mut state.files[state.current_file];
-                    let path = save_file_as(current_file.content.text());
-                    
-                    if path.is_some() {
-                        current_file.path = path;
-                    }
-                }
-                FileAction::Open => {
-                    let (path, content) = open_file();
-
-                    if let Some(opened_path) = &path {
-                        for (file_index, file) in state.files.iter_mut().enumerate() {
-                            if let Some(existing_path) = &file.path
-                                && opened_path == existing_path
-                            {
-                                file.content = text_editor::Content::with_text(&content);
-                                state.current_file = file_index;
-                                return Task::none();
-                            }
-                        }
-
-                        let opened_file = File {
-                            path,
-                            content: text_editor::Content::with_text(&content),
-                            markdown: markdown::parse(&content).collect(),
-                        };
-
-                        state.files.push(opened_file);
-                        state.current_file = state.files.len() - 1;
-                    }
-                }
-                FileAction::Save => {
-                    let current_file = &mut state.files[state.current_file];
-                    let path = save_file(current_file.path.clone(), current_file.content.text());
-                    current_file.path = path;
-                }
-                FileAction::New => {
-                    let default_file = File::default();
-
-                    state.files.push(default_file);
-                    state.current_file = state.files.len() - 1;
-                }
-                FileAction::Close(idx) => {
-                    let idx_to_close = match idx {
-                        Some(i) => i,
-                        None => state.current_file,
-                    };
-
-                    if state.files.len() == 1 {
-                        return iced::exit();
-                    }
-
-                    if state.files.len() - 1 == idx_to_close {
-                        state.current_file = state.files.len() - 2;
-                        state.files.remove(idx_to_close);
-                        return Task::none();
-                    }
-
-                    state.current_file -= 1;
-                    state.files.remove(idx_to_close);
-                }
-            }
-        }
-        Message::ViewActionSelected(action) => {
-            state.selected_view_action = None;
-
-            match action {
-                ViewAction::Increase => {
-                    if state.editor_font_size >= MAX_EDITOR_FONT_SIZE {
-                        return Task::none();
-                    }
-
-                    state.editor_font_size += 2;
-                }
-                ViewAction::Decrease => {
-                    if state.editor_font_size <= MIN_EDITOR_FONT_SIZE {
-                        return Task::none();
-                    }
-
-                    state.editor_font_size -= 2;
-                }
-                ViewAction::Reset => {
-                    state.editor_font_size = DEFAULT_EDITOR_FONT_SIZE;
-                }
-                // TODO: Carry previous mode when toggling
-                // so that if we already in preview we can
-                // just put the user back where they were
-                ViewAction::TogglePreview => match state.mode {
-                    Mode::Edit => state.mode = Mode::Preview,
-                    Mode::Preview => state.mode = Mode::Edit,
-                },
-            }
-        }
-        Message::SwitchTab(file_id) => {
-            state.current_file = file_id;
-        }
-        Message::LinkClicked(link) => {
-            print!("Link clicked: {}", link);
-        }
+        Message::Edit(action) => handler::edit(state, action),
+        Message::SwitchTab(index) => handler::switch_tab(state, index),
+        Message::LinkClicked(url) => handler::link_clicked(url),
+        Message::FileActionSelected(action) => handler::file_action(state, action),
+        Message::ViewActionSelected(action) => handler::view_action(state, action),
+        Message::FileOpened(result) => handler::opened_file(state, result),
+        Message::FileSaved(result) => handler::saved_file(state, result),
     }
-
-    Task::none()
 }
 
-struct Keybinding {
-    key: &'static str,
-    modifiers: Modifiers,
-    message: Message,
-}
-
-impl Keybinding {
-    fn should_handle(&self, key_pressed: &Key, modifiers: &Modifiers) -> bool {
-        let key = keyboard::Key::Character(self.key.into());
-        modifiers.contains(self.modifiers) && key_pressed == &key
-    }
+fn view(state: &State) -> Element<'_, Message> {
+    let current_file = state.active_file();
 
-    const ALL: &'static [Keybinding] = &[
-        Keybinding {
-            key: "o",
-            modifiers: Modifiers::CTRL,
-            message: Message::FileActionSelected(FileAction::Open),
-        },
-        Keybinding {
-            key: "n",
-            modifiers: Modifiers::CTRL,
-            message: Message::FileActionSelected(FileAction::New),
-        },
-        Keybinding {
-            key: "s",
-            modifiers: Modifiers::CTRL.union(Modifiers::SHIFT),
-            message: Message::FileActionSelected(FileAction::SaveAs),
-        },
-        Keybinding {
-            key: "s",
-            modifiers: Modifiers::CTRL,
-            message: Message::FileActionSelected(FileAction::Save),
-        },
-        Keybinding {
-            key: "w",
-            modifiers: Modifiers::CTRL,
-            message: Message::FileActionSelected(FileAction::Close(None)),
-        },
-        Keybinding {
-            key: "p",
-            modifiers: Modifiers::CTRL,
-            message: Message::ViewActionSelected(ViewAction::TogglePreview),
-        },
-        Keybinding {
-            key: "=",
-            modifiers: Modifiers::CTRL,
-            message: Message::ViewActionSelected(ViewAction::Increase),
-        },
-        Keybinding {
-            key: "-",
-            modifiers: Modifiers::CTRL,
-            message: Message::ViewActionSelected(ViewAction::Decrease),
-        },
-        Keybinding {
-            key: "0",
-            modifiers: Modifiers::CTRL,
-            message: Message::ViewActionSelected(ViewAction::Reset),
-        },
-    ];
+    column![
+        components::tabs::view(state.files(), state.current_file_index()),
+        components::action_bar::view(state.selected_file_action(), state.selected_view_action()),
+        components::editor::view(current_file, state.mode(), state.font_size()),
+        components::status_bar::view(current_file),
+    ]
+    .padding(10)
+    .into()
 }
 
 fn subscription(_state: &State) -> Subscription<Message> {
     event::listen_with(|e, _status, _win| -> Option<Message> {
         match e {
             iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) => {
-                for vb in Keybinding::ALL {
+                for vb in key_bindings::ALL {
                     if vb.should_handle(&key, &modifiers) {
-                        return Some(vb.message.clone());
+                        return Some(vb.message());
                     }
                 }
 
@@ -473,34 +80,6 @@ fn subscription(_state: &State) -> Subscription<Message> {
     })
 }
 
-fn boot() -> State {
-    let default_file = File::default();
-
-    let files = vec![default_file];
-
-    State {
-        files,
-        current_file: 0,
-        editor_font_size: DEFAULT_EDITOR_FONT_SIZE,
-        ..Default::default()
-    }
-}
-
-pub fn main() -> iced::Result {
-    iced::application(boot, update, view)
-        .subscription(subscription)
-        .font(include_bytes!("./fonts/CaskaydiaCoveNFM-Regular.ttf"))
-        .theme(theme)
-        .settings(iced::Settings {
-            default_font: CUSTOM_FONT,
-            ..Default::default()
-        })
-        .window(window::Settings {
-            icon: Some(
-                icon::from_file_data(include_bytes!("./images/icon.ico"), None)
-                    .expect("Failed to load icon"),
-            ),
-            ..window::Settings::default()
-        })
-        .run()
+fn theme(_state: &State) -> Theme {
+    Theme::Dark
 }
diff --git a/src/message.rs b/src/message.rs
new file mode 100644
index 0000000..e05f1dc
--- /dev/null
+++ b/src/message.rs
@@ -0,0 +1,73 @@
+use std::{fmt::Display, path::PathBuf};
+
+use iced::widget::text_editor;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FileAction {
+    New,
+    Save,
+    SaveAs,
+    Open,
+    Close(Option<usize>),
+}
+
+impl FileAction {
+    pub const ALL: &'static [FileAction] = &[
+        FileAction::New,
+        FileAction::Save,
+        FileAction::SaveAs,
+        FileAction::Open,
+        FileAction::Close(None),
+    ];
+}
+
+impl Display for FileAction {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            FileAction::New => write!(f, "New file"),
+            FileAction::Save => write!(f, "Save"),
+            FileAction::SaveAs => write!(f, "Save as... "),
+            FileAction::Open => write!(f, "Open"),
+            FileAction::Close(_) => write!(f, "Close"),
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ViewAction {
+    Increase,
+    Decrease,
+    Reset,
+    TogglePreview,
+}
+
+impl ViewAction {
+    pub const ALL: &'static [ViewAction] = &[
+        ViewAction::Increase,
+        ViewAction::Decrease,
+        ViewAction::Reset,
+        ViewAction::TogglePreview,
+    ];
+}
+
+impl Display for ViewAction {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ViewAction::Decrease => write!(f, "Decrease font"),
+            ViewAction::Increase => write!(f, "Increase font"),
+            ViewAction::Reset => write!(f, "Reset font"),
+            ViewAction::TogglePreview => write!(f, "Toggle preview"),
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+    Edit(text_editor::Action),
+    FileActionSelected(FileAction),
+    ViewActionSelected(ViewAction),
+    SwitchTab(usize),
+    LinkClicked(String),
+    FileOpened(Result<(PathBuf, String), String>),
+    FileSaved(Result<PathBuf, String>),
+}
diff --git a/src/state.rs b/src/state.rs
new file mode 100644
index 0000000..7ca7197
--- /dev/null
+++ b/src/state.rs
@@ -0,0 +1,147 @@
+use std::path::PathBuf;
+
+use iced::widget::text_editor;
+
+use crate::{constants, file};
+use crate::message::{FileAction, ViewAction};
+
+#[derive(Default, Copy, Clone)]
+pub enum Mode {
+    #[default]
+    Edit,
+    Preview,
+}
+
+#[derive(Default)]
+pub struct State {
+    mode: Mode,
+    files: Vec<file::File>,
+    current_file: usize,
+    editor_font_size: u32,
+    selected_file_action: Option<FileAction>,
+    selected_view_action: Option<ViewAction>,
+}
+
+impl State {
+    pub fn new() -> Self {
+        let default_file = file::File::default();
+
+        Self {
+            files: vec![default_file],
+            current_file: 0,
+            editor_font_size: constants::DEFAULT_EDITOR_FONT_SIZE,
+            ..Default::default()
+        }
+    }
+
+    pub fn apply_edit(&mut self, action: text_editor::Action) {
+        self.files[self.current_file].content_mut().perform(action);
+    }
+
+    pub fn new_file(&mut self) {
+        self.files.push(file::File::default());
+        self.current_file = self.files.len() - 1;
+    }
+
+    pub fn close_file(&mut self, index: Option<usize>) -> bool {
+        let index = index.unwrap_or(self.current_file);
+
+        if self.files.len() <= 1 {
+            return true;
+        }
+
+        self.files.remove(index);
+
+        if self.current_file >= self.files.len() {
+            self.current_file = self.files.len().saturating_sub(1);
+        } else if index < self.current_file {
+            self.current_file -= 1;
+        }
+
+        false
+    }
+
+    pub fn open_file(&mut self, path: PathBuf, content: String) {
+        if let Some(index) = self.files.iter().position(|f| f.path() == Some(&path)) {
+            self.files[index].set_content(&content);
+            self.current_file = index;
+            return;
+        }
+
+        let opened_file = file::File::from(&content, Some(path));
+
+        self.files.push(opened_file);
+        self.current_file = self.files.len() - 1;
+    }
+
+    pub fn switch_tab(&mut self, index: usize) {
+        if index < self.files.len() {
+            self.current_file = index;
+        }
+    }
+
+    pub fn active_file_data(&self) -> (Option<&PathBuf>, String) {
+        let file = &self.files[self.current_file];
+        (file.path(), file.content().text())
+    }
+
+    pub fn set_active_file_path(&mut self, path: PathBuf) {
+        if let Some(file) = self.files.get_mut(self.current_file) {
+            file.set_path(Some(path));
+        }
+    }
+
+    pub fn increase_font(&mut self) {
+        if self.editor_font_size < constants::MAX_EDITOR_FONT_SIZE {
+            self.editor_font_size += 2;
+        }
+    }
+
+    pub fn decrease_font(&mut self) {
+        if self.editor_font_size > constants::MIN_EDITOR_FONT_SIZE {
+            self.editor_font_size -= 2;
+        }
+    }
+
+    pub fn reset_font(&mut self) {
+        self.editor_font_size = constants::DEFAULT_EDITOR_FONT_SIZE;
+    }
+
+    pub fn toggle_preview(&mut self) {
+        match self.mode {
+            Mode::Edit => {
+                self.files[self.current_file].update_markdown();
+                self.mode = Mode::Preview;
+            }
+            Mode::Preview => self.mode = Mode::Edit,
+        }
+    }
+
+    pub fn files(&self) -> &[file::File] {
+        &self.files
+    }
+
+    pub fn current_file_index(&self) -> usize {
+        self.current_file
+    }
+
+    pub fn active_file(&self) -> &file::File {
+        &self.files[self.current_file]
+    }
+
+    pub fn mode(&self) -> Mode {
+        self.mode
+    }
+
+    pub fn font_size(&self) -> u32 {
+        self.editor_font_size
+    }
+
+    pub fn selected_file_action(&self) -> Option<FileAction> {
+        self.selected_file_action
+    }
+
+    pub fn selected_view_action(&self) -> Option<ViewAction> {
+        self.selected_view_action
+    }
+}
