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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Support dimming other areas while editing
- Remember the selected cell when switching between sheets
- Added optional lazy loading for xlsx and xlsb files to improve performance with large files (enabled with -l flag)

### Fixed

Expand All @@ -21,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Edit cell content using vim shortcuts
- Multiple UI improvements
- Replace ratatui_textarea with tui_textarea
- Upgraded calamine to version 0.27.0

## [0.2.0] - 2025-04-27

Expand Down
42 changes: 22 additions & 20 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ exclude = ["/.github", "CHANGELOG.md", ".gitignore"]
[dependencies]
ratatui = "0.24.0"
crossterm = "0.27.0"
calamine = "0.22.1"
calamine = "0.27.0"
anyhow = "1.0.79"
clap = { version = "4.5.0", features = ["derive"] }
rust_xlsxwriter = "0.86.0"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ excel-cli path/to/your/file.xlsx -j > data.json # (example) Save JSON output to
- `--json-export`, `-j`: Export all sheets to JSON and output to stdout (for piping)
- `--direction`, `-d`: Header direction in Excel: 'h' for horizontal (top rows), 'v' for vertical (left columns). Default: 'h'
- `--header-count`, `-r`: Number of header rows (for horizontal) or columns (for vertical) in Excel. Default: 1
- `--lazy-loading`, `-l`: Enable lazy loading for large Excel files (only loads data when needed)

## User Interface

Expand Down
1 change: 1 addition & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ excel-cli path/to/your/file.xlsx -j > data.json # (示例)将JSON输出保
- `--json-export`, `-j`:将所有工作表导出为 JSON 并输出到 stdout(用于管道传输)
- `--direction`, `-d`:Excel 中的表头方向:'h'表示水平(顶部行),'v'表示垂直(左侧列)。默认:'h'
- `--header-count`, `-r`:Excel 中的表头行数(水平方向)或列数(垂直方向)。默认:1
- `--lazy-loading`, `-l`:启用大型 Excel 文件的懒加载功能(仅在需要时加载数据)

## 用户界面

Expand Down
1 change: 1 addition & 0 deletions src/actions/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct CellAction {
}

impl CellAction {
#[must_use]
pub fn new(
sheet_index: usize,
sheet_name: String,
Expand Down
5 changes: 2 additions & 3 deletions src/actions/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ use super::types::{ActionCommand, ActionType};

impl ActionCommand {
// Returns the action type of this command
#[must_use]
pub fn get_action_type(&self) -> ActionType {
match self {
ActionCommand::Cell(action) => match action.action_type {
ActionType::Edit => ActionType::Edit,
ActionType::Cut => ActionType::Cut,
ActionType::Paste => ActionType::Paste,
_ => ActionType::Edit, // Default case
_ => ActionType::Edit, // Default case including Edit and Cut
},
ActionCommand::Row(_) => ActionType::DeleteRow,
ActionCommand::MultiRow(_) => ActionType::DeleteMultiRows,
Expand Down
2 changes: 2 additions & 0 deletions src/actions/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ impl Default for UndoHistory {
}

impl UndoHistory {
#[must_use]
pub fn new() -> Self {
Self {
undo_stack: Vec::with_capacity(100), // Pre-allocate capacity
Expand Down Expand Up @@ -44,6 +45,7 @@ impl UndoHistory {
}
}

#[must_use]
pub fn all_undone(&self) -> bool {
self.undo_stack.is_empty()
}
Expand Down
6 changes: 3 additions & 3 deletions src/app/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ impl AppState<'_> {
pub fn start_editing(&mut self) {
self.input_mode = InputMode::Editing;
let content = self.get_cell_content(self.selected_cell.0, self.selected_cell.1);
self.input_buffer = content.clone();
self.input_buffer.clone_from(&content);

// Initialize TextArea with content and settings
let mut text_area = tui_textarea::TextArea::default();
Expand Down Expand Up @@ -57,7 +57,7 @@ impl AppState<'_> {
let old_cell = self.workbook.get_current_sheet().data[row][col].clone();

let mut new_cell = old_cell.clone();
new_cell.value = content.clone();
new_cell.value.clone_from(&content);

let cell_action = CellAction::new(
sheet_index,
Expand Down Expand Up @@ -134,7 +134,7 @@ impl AppState<'_> {
let old_cell = self.workbook.get_current_sheet().data[row][col].clone();

let mut new_cell = old_cell.clone();
new_cell.value = content.clone();
new_cell.value.clone_from(&content);

let cell_action = CellAction::new(
sheet_index,
Expand Down
4 changes: 2 additions & 2 deletions src/app/navigation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ impl AppState<'_> {
|| sheet.data[row][col].value.is_empty();

let message = if is_cell_empty {
format!("Jumped to first non-empty cell ({})", dir_name)
format!("Jumped to first non-empty cell ({dir_name})")
} else {
format!("Jumped to last non-empty cell ({})", dir_name)
format!("Jumped to last non-empty cell ({dir_name})")
};

self.add_notification(message);
Expand Down
50 changes: 22 additions & 28 deletions src/app/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ impl AppState<'_> {

pub fn execute_search(&mut self) {
let query = self.text_area.lines().join("\n");
self.input_buffer = query.clone();
self.input_buffer.clone_from(&query);

if query.is_empty() {
self.input_mode = InputMode::Normal;
return;
}

// Save the query for n/N commands
self.search_query = query.clone();
self.search_query.clone_from(&query);

// Set search direction based on mode
match self.input_mode {
Expand All @@ -53,7 +53,7 @@ impl AppState<'_> {
self.search_results = self.find_all_matches(&query);

if self.search_results.is_empty() {
self.add_notification(format!("Pattern not found: {}", query));
self.add_notification(format!("Pattern not found: {query}"));
self.current_search_idx = None;
} else {
// Find the appropriate result to jump to based on search direction and current position
Expand Down Expand Up @@ -87,7 +87,7 @@ impl AppState<'_> {
continue;
}

if self.case_insensitive_contains(cell_content, &query_lower) {
if Self::case_insensitive_contains(cell_content, &query_lower) {
results.push((row, col));
}
}
Expand All @@ -97,7 +97,7 @@ impl AppState<'_> {
results
}

fn case_insensitive_contains(&self, haystack: &str, needle: &str) -> bool {
fn case_insensitive_contains(haystack: &str, needle: &str) -> bool {
if needle.is_empty() {
return true;
}
Expand All @@ -123,36 +123,30 @@ impl AppState<'_> {
pos.0 > current_pos.0 || (pos.0 == current_pos.0 && pos.1 > current_pos.1)
});

match next_idx {
Some(idx) => {
self.current_search_idx = Some(idx);
self.selected_cell = self.search_results[idx];
}
None => {
// Wrap around to the first result
self.current_search_idx = Some(0);
self.selected_cell = self.search_results[0];
self.add_notification("Search wrapped to top".to_string());
}
if let Some(idx) = next_idx {
self.current_search_idx = Some(idx);
self.selected_cell = self.search_results[idx];
} else {
// Wrap around to the first result
self.current_search_idx = Some(0);
self.selected_cell = self.search_results[0];
self.add_notification("Search wrapped to top".to_string());
}
} else {
// Backward search
let prev_idx = self.search_results.iter().rposition(|&pos| {
pos.0 < current_pos.0 || (pos.0 == current_pos.0 && pos.1 < current_pos.1)
});

match prev_idx {
Some(idx) => {
self.current_search_idx = Some(idx);
self.selected_cell = self.search_results[idx];
}
None => {
// Wrap around to the last result
let last_idx = self.search_results.len() - 1;
self.current_search_idx = Some(last_idx);
self.selected_cell = self.search_results[last_idx];
self.add_notification("Search wrapped to bottom".to_string());
}
if let Some(idx) = prev_idx {
self.current_search_idx = Some(idx);
self.selected_cell = self.search_results[idx];
} else {
// Wrap around to the last result
let last_idx = self.search_results.len() - 1;
self.current_search_idx = Some(last_idx);
self.selected_cell = self.search_results[last_idx];
self.add_notification("Search wrapped to bottom".to_string());
}
}

Expand Down
Loading