Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/tauri/0.prerequisites/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ title: Windows

## **Step 1: Install Required Software**

### If you do not have a packate manager & JS/TS runtime (node, npm)
### 1. Install Deno (JavaScript/TypeScript Runtime)

- **Website**: [https://deno.land/](https://deno.land/)
Expand Down
103 changes: 103 additions & 0 deletions docs/tauri/2.build-your-own-text-editor/0.rust-fundamentals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: Rust Essentials
---

# 🛠️ Rust Essentials: The Foundation

**"Before we build the editor, we must calibrate our tools."**

This section covers the essential setup and the two specific concepts—**Error Handling** and **Pattern Matching**—that are required to complete the upcoming tasks. We will establish a workflow that ensures memory safety without the manual overhead typical of older systems languages.

---

### **1. The Build System: Cargo**

In languages like C or C++, setting up a project often involves configuring build systems (Make, CMake) and package managers manually. Rust consolidates the compiler (`rustc`), build system, and dependency manager into a single tool: **Cargo**.

1. **Verify Installation:**
Open your terminal and run: `cargo --version`
2. **Create a Sandbox Project:**
We will create a temporary project to test our logic before adding it to the Tauri app.
```bash
cargo new rust-warmup
cd rust-warmup
```

### **2. The Concept: The `Result` Type**

In C or C++, system calls (like opening a file) often return an integer status code (e.g., `-1` for failure) or throw an exception. This requires the developer to remember to check that specific integer or wrap code in try/catch blocks.

Rust handles this differently to prevent undefined behavior. It uses the **`Result`** type.

A `Result` is an enum that contains **one** of two possible values:

- **`Ok(T)`**: The operation succeeded, containing the data (`T`).
- **`Err(E)`**: The operation failed, containing the error details (`E`).

You **cannot** access the data inside `Ok` without first handling the possibility of `Err`. This ensures that I/O errors are never silently ignored.

### **3. The Exercise: Safe File Reading**

Open `src/main.rs` in your `rust-warmup` folder. We will write a function that attempts to read a file that does not exist to demonstrate how Rust forces safety.

**Step 1: Import the filesystem module**

```rust
use std::fs;
```

**Step 2: Implement the Logic**
We will use **`match`**, which is similar to a `switch` statement in C++, but with a critical difference: it is **exhaustive**. The compiler will refuse to build your code if you do not handle every possible outcome (Success and Failure).

```rust
use std::fs;

fn main() {
// Try to read a file that definitely doesn't exist
let filename = "ghost_file.txt";

// This call does NOT return a String.
// It returns a Result<String, std::io::Error>
let result = fs::read_to_string(filename);

// We must 'unwrap' the Result to get the value
match result {
Ok(content) => {
// This block executes ONLY if the OS successfully read the file
println!("File content: {}", content);
},
Err(error) => {
// This block executes if ANY system error occurred (Permission denied, Not found, etc.)
// In C++, this might have caused a crash if not caught.
println!("⚠️ Error reading '{}'", filename);
println!("System Message: {}", error);
}
}
}
```

### **4. Execution**

Run the project:

```bash
cargo run
```

**Expected Output:**

```text
⚠️ Error reading 'ghost_file.txt'
System Message: The system cannot find the file specified. (os error 2)
```

### **Relevance to the Course**

In the upcoming Text Editor tasks, every interaction between your UI and the Operating System will follow this pattern:

1. Attempt an OS action (Read/Write).
2. Receive a `Result`.
3. Match the result to send either **Data** or an **Error Message** back to the user.

**"Safety is not an option you enable; it is the default state of the language."**
_Ready? Let's build the editor._ 🚀
135 changes: 135 additions & 0 deletions docs/tauri/2.build-your-own-text-editor/1.tauri-arhitecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
title: Tauri Architecture
---

# Tauri Architecture: The Rust-TS Bridge

**"A bridge isn't just a path; it's a contract between two distinct lands."**

You have calibrated your Rust tools. Now, we must understand the "map" of a Tauri application.

A Tauri app is **not** one single program. It is two applications running in harmony:

1. **The Backend (Rust):** A native, high-performance system process. This is the "Engine."
2. **The Frontend (WebView):** A modern web browser view running your HTML, CSS, and TypeScript. This is the "Cockpit."

This document explains the "medium" or "bridge" that allows them to communicate.

-----

### **1. The Project Structure**

When you create a Tauri project, you will see two primary folders. Understanding this separation is key.

```plaintext
my-text-editor/
├── src/
│ ├── main.ts # Your Frontend (TypeScript)
│ ├── index.html # Your Frontend (HTML)
│ └── ... # (CSS, other TS files)
├── src-tauri/
│ ├── src/
│ │ └── main.rs # Your Backend (Rust)
│ ├── Cargo.toml # Your Backend's dependencies
│ └── tauri.conf.json # The "Manifest" that links them
└── ...
```

* **`src/`**: This is a standard web project. It has no access to the filesystem or any native features *until* you ask Rust for them.
* **`src-tauri/`**: This is a standard Rust project. It has no UI *until* you attach the WebView to it.
* **`tauri.conf.json`**: This file is the architect. It defines the app's window, sets security permissions, and tells the Rust backend which frontend to load.

-----

### **2. The Backend: Exposing a Command**

You cannot call a Rust function from TypeScript directly. You must "expose" it as a **Tauri Command**.

This is done with a macro: `#[tauri::command]`.

Let's look at a simple example in `src-tauri/src/main.rs`. We'll use the `Result` type you learned about in the "Rust Essentials" module.

```rust
// In src-tauri/src/main.rs

// This macro transforms the Rust function into something Tauri can manage
#[tauri::command]
fn greet(name: String) -> Result<String, String> {
if name.is_empty() {
// If we return an Err, it rejects the promise in TypeScript
Err("Name cannot be empty!".to_string())
} else {
// If we return Ok, it resolves the promise in TypeScript
Ok(format!("Hello, {}!", name))
}
}
```

-----

### **3. The Handler: The Rust "Switchboard"**

Just defining the command isn't enough. We must register it with Tauri's **Invoke Handler** in our `main()` function.

Think of the handler as a "switchboard" or "function router." It listens for string-based messages from the frontend and routes them to the correct Rust function.

```rust
// In src-tauri/src/main.rs

fn main() {
tauri::Builder::default()
// This line registers our 'greet' command
.invoke_handler(tauri::generate_handler![
greet
// All other commands you write will be listed here
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```

-----

### **4. The Frontend: Calling a Command**

Now for the final piece: calling our `greet` command from TypeScript. We use the **`invoke`** function from the `@tauri-apps/api` library.

`invoke` is an **async** function. It sends a message to Rust and waits for a response.

```typescript
// In src/main.ts
import { invoke } from '@tauri-apps/api';

async function sayHello() {
try {
// 1. We call 'invoke' with the Rust function's name (snake_case)
// 2. We pass arguments in an object (camelCase keys)
const response: string = await invoke('greet', { name: 'World' });

// This runs if Rust returned Ok()
console.log(response); // "Hello, World!"

} catch (error) {
// This runs if Rust returned Err()
console.error(error); // "Name cannot be empty!"
}
}

// Example of the error case
sayHello();
await invoke('greet', { name: '' }); // This will trigger the catch block
```

### **The Contract: `Result` ↔ `Promise`**

You have just seen the "medium" in action. It's a simple, powerful contract:

> * A Rust `Result::Ok(value)` **resolves** the JavaScript `Promise(value)`.
> * A Rust `Result::Err(error)` **rejects** the JavaScript `Promise(error)`.

This allows you to write safe, robust Rust code (as seen in "Rust Essentials") and handle errors gracefully in your frontend using standard `try...catch` blocks.

**"The bridge is built. Now, let's send traffic."**
*Ready to build the editor's core?* 🔧
90 changes: 90 additions & 0 deletions docs/tauri/2.build-your-own-text-editor/5.showing-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: The System Inspector
---

# Task 4: The System Inspector

---

_"Reading the text is simple. Reading the system attributes is where engineering begins."_

In standard C/C++, accessing file metadata (size, permissions, timestamps) involves using the `stat` family of structs and system calls, which can vary significantly between Windows and Linux/macOS.

Rust abstracts this via `std::fs::metadata`, but introduces a new challenge: **Data Serialization**.

**Your Mission:** Add a "File Info" panel that displays system-level metadata for the currently open file.

---

### **1. The Concept: Struct Serialization**

To send a complex C-style struct from Rust to a JavaScript frontend, we cannot simply pass the memory address. We must serialize the data into a format the frontend understands (JSON).

Rust uses a standard crate for this called **Serde** (SERializer/DEserializer).

1. Open `Cargo.toml` and add `serde` to your dependencies:
```toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
# ... existing dependencies
```

### **2. Backend Challenge (Rust)**

You need to query the filesystem for metadata and map it to a struct.

**Requirements:**

1. Define a Rust `struct` representing the file stats.
2. Derive `Serialize` so Tauri can convert it to JSON automatically.
3. Use `std::fs::metadata` to populate the data.

**Starter Code:**

```rust
use std::fs;
use serde::Serialize;

// This struct will be converted to a JSON object automatically
#[derive(Serialize)]
struct FileStats {
size_bytes: u64,
is_readonly: bool,
// Challenge: 'modified' returns SystemTime, which is not directly serializable to JSON.
// You must figure out how to convert SystemTime to a UNIX timestamp (u64) or String.
last_modified: u64,
}

#[tauri::command]
fn get_file_stats(path: String) -> Result<FileStats, String> {
// TODO:
// 1. Call fs::metadata(&path)
// 2. Handle the Result (match or ?)
// 3. Map the Metadata struct to your FileStats struct
}
```

### **3. Frontend Challenge (TypeScript)**

1. Create a "Properties" view or a footer bar in your editor.
2. When a file is active, invoke `get_file_stats`.
3. Display the size.
- _Math Challenge:_ Convert the raw bytes into human-readable units (KB, MB, GB).

### **4. Deep Dive: The Time Problem**

In C++, `time_t` is usually just an integer. In Rust, `SystemTime` is an opaque type designed to be platform-independent.

- **The Hurdle:** You cannot send `SystemTime` directly to JS.
- **The Hint:** Look for `duration_since(UNIX_EPOCH)` in the Rust documentation to convert the time object into a simple number that JavaScript's `new Date()` can parse.

### **Conclusion Task 4**

Completing this task demonstrates mastery of:

1. **System Abstraction:** Using `std::fs` to normalize OS-specific file attributes.
2. **FFI (Foreign Function Interface):** Marshalling complex data structures between Rust and JS via Serde.

**"You now control not just the content, but the container."** 🦀✨

---