diff --git a/.gitignore b/.gitignore
index 82d4746..976b3b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,8 +23,7 @@ nul
data/
# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
+.vscode/
.idea
.DS_Store
*.suo
@@ -32,3 +31,21 @@ data/
*.njsproj
*.sln
*.sw?
+
+# AI/LLM tooling
+.claude/
+.cursor/
+.copilot/
+.codeium/
+.tabnine/
+.aider*
+.continue/
+
+# Trash
+trash/
+
+# Docs
+docs/
+
+# This file
+.gitignore
diff --git a/public/tauri.svg b/public/tauri.svg
deleted file mode 100644
index 31b62c9..0000000
--- a/public/tauri.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 905df4b..9f560a2 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -3703,6 +3703,21 @@ dependencies = [
"zbus",
]
+[[package]]
+name = "tauri-plugin-single-instance"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc61e4822b8f74d68278e09161d3e3fdd1b14b9eb781e24edccaabf10c420e8c"
+dependencies = [
+ "serde",
+ "serde_json",
+ "tauri",
+ "thiserror 2.0.18",
+ "tracing",
+ "windows-sys 0.60.2",
+ "zbus",
+]
+
[[package]]
name = "tauri-runtime"
version = "2.10.0"
@@ -4281,7 +4296,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vesper"
-version = "0.1.0"
+version = "1.0.1"
dependencies = [
"serde",
"serde_json",
@@ -4290,6 +4305,7 @@ dependencies = [
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-opener",
+ "tauri-plugin-single-instance",
]
[[package]]
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 4894863..1a4b42c 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -22,3 +22,6 @@ tauri-plugin-fs = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
+[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
+tauri-plugin-single-instance = "2"
+
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 7068e58..e3cb705 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -39,6 +39,31 @@ fn save_window_state(state: &WindowState) {
}
}
+#[derive(Serialize, Clone)]
+struct CliFile {
+ path: String,
+ content: String,
+}
+
+/// Try to read a markdown file from the given path string.
+fn read_md_file(file_arg: &str) -> Option {
+ let path = PathBuf::from(file_arg);
+ if !path.is_file() { return None; }
+ let ext = path.extension()?.to_str()?.to_lowercase();
+ if ext != "md" && ext != "markdown" && ext != "txt" { return None; }
+ let content = fs::read_to_string(&path).ok()?;
+ Some(CliFile {
+ path: path.to_string_lossy().to_string(),
+ content,
+ })
+}
+
+/// Returns the file path + content if the app was launched with a .md/.markdown/.txt argument.
+#[tauri::command]
+fn get_cli_file() -> Option {
+ env::args().nth(1).and_then(|a| read_md_file(&a))
+}
+
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let data_dir = get_data_dir();
@@ -51,9 +76,23 @@ pub fn run() {
);
tauri::Builder::default()
+ .plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
+ // Second instance launched with a file — send it to the existing window
+ if let Some(file_arg) = args.get(1) {
+ if let Some(cli_file) = read_md_file(file_arg) {
+ let _ = app.emit("open-file", cli_file);
+ }
+ }
+ // Focus the existing window
+ if let Some(window) = app.get_webview_window("main") {
+ let _ = window.unminimize();
+ let _ = window.set_focus();
+ }
+ }))
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
+ .invoke_handler(tauri::generate_handler![get_cli_file])
.setup(|app| {
let state = load_window_state();
let window = app.get_webview_window("main").unwrap();
@@ -64,35 +103,12 @@ pub fn run() {
));
}
if let (Some(w), Some(h)) = (state.width, state.height) {
- let _ = window.set_size(tauri::Size::Physical(
- tauri::PhysicalSize::new(w, h),
- ));
+ let _ = window.set_size(tauri::Size::Physical(tauri::PhysicalSize::new(w, h)));
}
if let Some(true) = state.maximized {
let _ = window.maximize();
}
- // If launched with a file argument (e.g. double-clicking a .md file),
- // emit the path to the frontend so it can open it as a tab.
- let args: Vec = env::args().collect();
- if let Some(file_arg) = args.get(1) {
- let path = PathBuf::from(file_arg);
- if path.is_file() {
- if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
- let ext_lower = ext.to_lowercase();
- if ext_lower == "md" || ext_lower == "markdown" || ext_lower == "txt" {
- let path_str = path.to_string_lossy().to_string();
- let win = window.clone();
- // Emit after a short delay so the frontend has time to set up listeners
- std::thread::spawn(move || {
- std::thread::sleep(std::time::Duration::from_millis(500));
- let _ = win.emit("open-file", path_str);
- });
- }
- }
- }
- }
-
Ok(())
})
.on_window_event(|window, event| {
diff --git a/src/assets/react.svg b/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/test-markdown.md b/test-markdown.md
deleted file mode 100644
index 0906ff9..0000000
--- a/test-markdown.md
+++ /dev/null
@@ -1,332 +0,0 @@
-# Markdown Combinations Test
-
-This file tests all possible combinations of markdown elements.
-
----
-
-## 1. Lists with Text Formatting
-
-### Bold in List
-
-- **Bold item 1**
-- **Bold item 2** with more text
-- **Bold item 3** and *italic* and `code`
-
-### Italic in List
-
-- *Italic item 1*
-- *Italic item 2* with text
-- *Italic* and **bold** together
-
-### Code in List
-
-- `inline code` item
-- Code with `const x = 1` variable
-- Command `npm install`
-
-### Mixed Formatting in List
-
-- **Bold** and *italic* and `code` all together
-- ***Bold italic*** with normal text
-- ~~Strikethrough~~ and **bold**
-- ==Highlighted== and *italic*
-
----
-
-## 2. Headings in Lists
-
-- ### Heading 3 in list
-- #### Heading 4 in list
-- ##### Heading 5 in list
-
-1. ### Heading 3 in ordered list
-2. #### Heading 4 in ordered list
-3. ##### Heading 5 in ordered list
-
----
-
-## 3. Blockquotes with Lists
-
-> ### Blockquote with list:
-> - Item 1
-> - Item 2
-> - Item 3
-
-> **Bold blockquote** with list:
-> 1. First
-> 2. Second
-> 3. Third
-
-> *Italic blockquote* with `code`:
-> - Item with `inline code`
-> - Another with **bold**
-
----
-
-## 4. Lists in Blockquotes
-
-> This is a blockquote with a nested list:
-> - First level
-> - Second level
-> - Third level
-> - Fourth level
-> - Back to first
-
-> Another blockquote with mixed content:
-> - **Bold item**
-> - *Italic item*
-> - `Code item`
-> - [Link item](https://example.com)
-
----
-
-## 5. Links in Various Elements
-
-### Links in Lists
-
-- [Link](https://example.com) in list
-- [Link with title](https://example.com "Title") in list
-- [Reference link][ref] in list
-
-[ref]: https://example.com
-
-### Links in Blockquotes
-
-> [Link in blockquote](https://example.com)
-> **[Bold link](https://example.com)**
-> *[[Italic link](https://example.com)*
-
-### Links in Code
-
-- `Link` in list
-- `[link](url)` as code
-
----
-
-## 6. Images in Various Elements
-
-### Images in Lists
-
--  in list
-- 
-
-### Images in Blockquotes
-
-> 
-
----
-
-## 7. Code Blocks with Formatting
-
-### Code Block with Bold/Italic
-
-```
-**This is bold in code**
-*This is italic in code*
-`inline code in code block`
-```
-
-### Code Block Inside List
-
-- ```
- // Code block in list
- const x = 1;
- ```
-- Another item
-
----
-
-## 8. Tables with Formatting
-
-| Header | **Bold Header** | *Italic Header* |
-|--------|-----------------|-----------------|
-| Cell | **Bold Cell** | *Italic Cell* |
-| `Code` | **Bold** + `code` | *Italic* + `code` |
-
----
-
-## 9. Nested Lists with All Formatting
-
-- Level 1 **bold**
- - Level 2 *italic*
- - Level 3 `code`
- - Level 4 ~~strikethrough~~
- - Level 5 ==highlight==
-
-1. Ordered 1 **bold**
- 1. Ordered 1.1 *italic*
- 1. Ordered 1.1.1 `code`
-
----
-
-## 10. Complex Combinations
-
-### Blockquote > List > Blockquote
-
-> - Item 1
-> > Nested blockquote
-> > With **bold**
-> - Item 2
-
-### List > Blockquote > List
-
-- Item 1
- > Blockquote inside list
- > With *italic*
-- Item 2
-
-### List > Code > List
-
-- Item 1
- ```
- // Code block nested
- const x = 1;
- ```
-- Item 2
-
-### Task List with Formatting
-
-- [x] **Completed** task with *italic*
-- [ ] *Incomplete* with `code`
-- [x] ==Highlighted== and **bold** and *italic*
-
----
-
-## 11. Heading + List Combinations
-
-### Heading followed by formatted list
-
-#### Subheading
-
-- **Bold item**
-- *Italic item*
-- `Code item`
-
-### Multiple headings in sequence
-
-## Section 1
-
-- Item 1
-- Item 2
-
-## Section 2
-
-1. Ordered 1
-2. Ordered 2
-
-### Heading within list (edge case)
-
-- Normal item
-- #### Not a heading (just text)
-- Normal item
-
----
-
-## 12. Links + Code + Bold + Italic combinations
-
-- **[Bold link](https://example.com)**
-- *[Italic link](https://example.com)*
-- [`Code link`](https://example.com)
-- **[Link](https://example.com)** with `code` and *italic*
-- ***[All combined](https://example.com)*** with `more code`
-
----
-
-## 13. Tables + Lists combinations
-
-| Table | With | Formatting |
-|-------|------|-----------|
-| - List in table | - Another | - Item |
-| **Bold** | *Italic* | `Code` |
-
-- Table below list:
-
-| Col1 | Col2 |
-|------|------|
-| A | B |
-
-- List below table:
-
- | Col1 | Col2 |
- |------|------|
- | C | D |
-
----
-
-## 14. All Elements in One Paragraph
-
-This is a paragraph with **bold**, *italic*, ***bold italic***, `inline code`, [a link](https://example.com), , ~~strikethrough~~, ==highlight==, and ^superscript^, all in one paragraph. It's quite long and should test how your renderer handles multiple formatting elements combined.
-
----
-
-## 15. Edge Case: Empty Elements
-
--
-- Item after empty
-
->
-> Empty blockquote
-
----
-
-## 16. Deep Nesting
-
-1. Level 1
- - Level 2
- - Level 3
- - Level 4
- - Level 5
- - Level 6
- - **Deep bold**
- - *Deep italic*
- - `Deep code`
-
----
-
-## 17. Code Blocks with Language and Formatting
-
-```javascript
-// JavaScript with **fake bold** (just text)
-const greet = (name) => {
- return `Hello, ${name}!`; // Template literal
-};
-```
-
-```python
-# Python with *italic* comments
-def hello():
- """Docstring with **bold**"""
- return "Hello"
-```
-
-```html
-
-
-
Paragraph with *italic*
-
-```
-
----
-
-## 18. Final Complex Example
-
-> ### Blockquote Heading
->
-> This is a blockquote that contains:
->
-> - A **bold** list item
-> - An *italic* list item
-> - A `code` list item
-> - A [link](https://example.com) item
->
-> And then continues with more text that has **bold**, *italic*, and `code` formatting.
->
-> It also has a nested list:
->
-> 1. First nested **bold** item
-> 2. Second nested *italic* item
-> 3. Third nested `code` item
-
----
-
-This covers virtually every possible combination of markdown elements!