Generalize pane visibility with ShowPane/HidePane actions
Replace PopupHelp and ToggleFinder with a general pane visibility system:
- Add PanePosition enum (Main, Floating, future: Sidebar, Split)
- ShowPane { buffer, position } displays a buffer in specified layout
- HidePane { buffer, delete_buffer } removes from layout, optionally deletes
Commands now compose LoadBuffer + ShowPane for popups. Removed FinderBuffer
and Buffer::Finder variant - all buffers are now TextBuffer.
Also adds jj preference to CLAUDE.md, and does some other refactoring
This commit is contained in:
@@ -2,6 +2,21 @@
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Version Control
|
||||
|
||||
This repository uses both jujutsu (jj) and git. **Prefer jj commands** for all VCS operations:
|
||||
|
||||
```bash
|
||||
jj status # Instead of git status
|
||||
jj diff # Instead of git diff
|
||||
jj log # Instead of git log
|
||||
jj new -m "message" # Create new change with description
|
||||
jj describe -m "msg" # Update current change's description
|
||||
jj squash # Fold into parent
|
||||
```
|
||||
|
||||
Use git only when jj doesn't support the operation (e.g., `git push`).
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
|
||||
+12
-29
@@ -10,16 +10,14 @@ mod pane_layout;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use tracing::{error, info};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
configuration::Configuration,
|
||||
core::{
|
||||
actions::{BufferDestination, BufferSource, CommandAction, EditorAction},
|
||||
buffer::FinderBuffer,
|
||||
buffer_query::BufferQuery,
|
||||
error::BasicErrorKind,
|
||||
pane_layout::PaneDescriptor,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -112,7 +110,6 @@ impl EditorCore {
|
||||
// Get a query interface to the active buffer for movement computation
|
||||
let buffer_query: Option<&dyn BufferQuery> = match self.active_buffer() {
|
||||
Buffer::Text(tb) => Some(tb),
|
||||
Buffer::Finder(_) => None, // Finder doesn't support cursor movement yet
|
||||
};
|
||||
|
||||
let action = self
|
||||
@@ -218,34 +215,20 @@ impl EditorCore {
|
||||
tb.set_cursor(x, y);
|
||||
}
|
||||
}
|
||||
PopupHelp(toggle) => {
|
||||
info!(%toggle, "invoke help");
|
||||
let keys = self.buffers.keys();
|
||||
info!(?keys, "buffers");
|
||||
|
||||
//TODO change this hardcoding of the buffer id
|
||||
let buffer_id = BufferId(1);
|
||||
if toggle {
|
||||
self.buffers
|
||||
.insert(buffer_id, Buffer::Text(TextBuffer::help()));
|
||||
let descriptor = PaneDescriptor::Floating;
|
||||
self.pane_layout.add(buffer_id, descriptor);
|
||||
ShowPane { buffer, position } => {
|
||||
if self.buffers.contains_key(&buffer) {
|
||||
self.pane_layout.add(buffer, position);
|
||||
} else {
|
||||
self.buffers.remove(&buffer_id);
|
||||
self.pane_layout.remove(buffer_id);
|
||||
error!(?buffer, "ShowPane: buffer does not exist");
|
||||
}
|
||||
}
|
||||
ToggleFinder(toggle) => {
|
||||
//TODO change this hardcoding of the buffer id
|
||||
let buffer_id = BufferId(100);
|
||||
if toggle {
|
||||
let buffer = Buffer::Finder(FinderBuffer::new());
|
||||
self.buffers.insert(buffer_id, buffer);
|
||||
let descriptor = PaneDescriptor::Floating;
|
||||
self.pane_layout.add(buffer_id, descriptor);
|
||||
} else {
|
||||
self.buffers.remove(&buffer_id);
|
||||
self.pane_layout.remove(buffer_id);
|
||||
HidePane {
|
||||
buffer,
|
||||
delete_buffer,
|
||||
} => {
|
||||
self.pane_layout.remove(buffer);
|
||||
if delete_buffer {
|
||||
self.buffers.remove(&buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
-4
@@ -5,6 +5,21 @@ use std::path::PathBuf;
|
||||
/// `EditorAction`s are defined here as well.
|
||||
use crate::core::{buffer::BufferId, Mode};
|
||||
|
||||
/// Describes how and where a pane should be displayed.
|
||||
///
|
||||
/// This allows scripts to control pane positioning without EditorCore
|
||||
/// needing specific knowledge of each UI pattern (popups, sidebars, splits, etc.)
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum PanePosition {
|
||||
/// Main editing area, with tab ordering
|
||||
Main { tab: u16 },
|
||||
/// Floating popup/dialog centered over the main area
|
||||
Floating,
|
||||
// Future possibilities:
|
||||
// Sidebar { side: Side, width: u16 },
|
||||
// Split { direction: Direction, ratio: f32 },
|
||||
}
|
||||
|
||||
/// Describes where buffer content comes from.
|
||||
///
|
||||
/// This abstraction allows the scripting layer to populate buffers from various sources
|
||||
@@ -79,10 +94,20 @@ pub(crate) enum EditorAction {
|
||||
destination: Option<BufferDestination>,
|
||||
},
|
||||
|
||||
/// Show or hide the popup help
|
||||
PopupHelp(bool),
|
||||
/// Show or hide the Finder
|
||||
ToggleFinder(bool),
|
||||
/// Show a buffer in a pane at the specified position.
|
||||
/// The buffer must already exist (use LoadBuffer first if needed).
|
||||
ShowPane {
|
||||
buffer: BufferId,
|
||||
position: PanePosition,
|
||||
},
|
||||
|
||||
/// Hide a pane, optionally deleting the buffer.
|
||||
/// If `delete_buffer` is true, the buffer is also removed from memory.
|
||||
HidePane {
|
||||
buffer: BufferId,
|
||||
delete_buffer: bool,
|
||||
},
|
||||
|
||||
//TODO rename this to something like commanderror
|
||||
Error(String),
|
||||
}
|
||||
|
||||
@@ -13,23 +13,9 @@ use super::{buffer_settings::BufferSettings, cursor::Cursor, error::BasicError};
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct BufferId(pub(crate) usize);
|
||||
|
||||
// TODO: Consider whether Buffer enum is still needed, or if everything is just TextBuffer
|
||||
pub(crate) enum Buffer {
|
||||
Text(TextBuffer),
|
||||
Finder(FinderBuffer),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FinderBuffer {
|
||||
title: Option<String>,
|
||||
pub(crate) input_buffer: Option<String>,
|
||||
}
|
||||
impl FinderBuffer {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
title: None,
|
||||
input_buffer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO make these settings nonpublic, except for some setting that indicates style
|
||||
@@ -70,13 +56,6 @@ impl TextBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn help() -> Self {
|
||||
let mut buf = Self::from_text("Dummy help text\nA newline\nSome other stuff desu");
|
||||
buf.writeable = false;
|
||||
buf.title = Some("Help".to_string());
|
||||
buf
|
||||
}
|
||||
|
||||
pub(crate) fn title(&self) -> Option<&str> {
|
||||
self.title.as_ref().map(|x| x.as_str())
|
||||
}
|
||||
@@ -3,10 +3,32 @@ use std::path::PathBuf;
|
||||
use tracing::info;
|
||||
|
||||
use crate::core::{
|
||||
actions::{BufferDestination, BufferSource},
|
||||
actions::{BufferDestination, BufferSource, PanePosition},
|
||||
buffer::BufferId,
|
||||
EditorAction,
|
||||
};
|
||||
|
||||
// Well-known buffer IDs for standard panes
|
||||
// TODO: these should probably be managed more dynamically
|
||||
const HELP_BUFFER_ID: BufferId = BufferId(1);
|
||||
const FINDER_BUFFER_ID: BufferId = BufferId(100);
|
||||
|
||||
const HELP_TEXT: &str = "\
|
||||
Pane Editor - Help
|
||||
|
||||
Navigation (Normal mode):
|
||||
h/j/k/l - Move cursor left/down/up/right
|
||||
i - Enter Insert mode
|
||||
: - Enter Command mode
|
||||
q - Quit
|
||||
|
||||
Commands:
|
||||
:open <path> - Open a file
|
||||
:write [path] - Save buffer (to path or backing file)
|
||||
:help - Show this help
|
||||
:quit - Quit the editor
|
||||
";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CommandAPI {}
|
||||
|
||||
@@ -50,16 +72,43 @@ impl CommandAPI {
|
||||
Command::Quit => vec![EditorAction::Quit],
|
||||
Command::Help => {
|
||||
let toggle = commands.next();
|
||||
let toggle = match toggle {
|
||||
Some("on") => true,
|
||||
None => true,
|
||||
let show = match toggle {
|
||||
Some("on") | None => true,
|
||||
Some("off") => false,
|
||||
_ => return vec![],
|
||||
};
|
||||
info!("showing help");
|
||||
vec![EditorAction::PopupHelp(toggle)]
|
||||
if show {
|
||||
vec![
|
||||
EditorAction::LoadBuffer {
|
||||
buffer: Some(HELP_BUFFER_ID),
|
||||
source: BufferSource::Text(HELP_TEXT.to_string()),
|
||||
},
|
||||
EditorAction::ShowPane {
|
||||
buffer: HELP_BUFFER_ID,
|
||||
position: PanePosition::Floating,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
vec![EditorAction::HidePane {
|
||||
buffer: HELP_BUFFER_ID,
|
||||
delete_buffer: true,
|
||||
}]
|
||||
}
|
||||
}
|
||||
Command::Finder => {
|
||||
// TODO: Finder needs special buffer type, for now just show empty
|
||||
vec![
|
||||
EditorAction::LoadBuffer {
|
||||
buffer: Some(FINDER_BUFFER_ID),
|
||||
source: BufferSource::Empty,
|
||||
},
|
||||
EditorAction::ShowPane {
|
||||
buffer: FINDER_BUFFER_ID,
|
||||
position: PanePosition::Floating,
|
||||
},
|
||||
]
|
||||
}
|
||||
Command::Finder => vec![EditorAction::ToggleFinder(true)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-11
@@ -1,21 +1,22 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::actions::PanePosition;
|
||||
use super::buffer::BufferId;
|
||||
|
||||
pub(crate) struct PaneLayout {
|
||||
map: HashMap<BufferId, PaneDescriptor>,
|
||||
map: HashMap<BufferId, PanePosition>,
|
||||
}
|
||||
|
||||
impl PaneLayout {
|
||||
pub(super) fn new(initial_id: BufferId) -> Self {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(initial_id, PaneDescriptor::Main { tab: 0 });
|
||||
map.insert(initial_id, PanePosition::Main { tab: 0 });
|
||||
|
||||
Self { map }
|
||||
}
|
||||
|
||||
pub(super) fn add(&mut self, id: BufferId, descriptor: PaneDescriptor) {
|
||||
self.map.insert(id, descriptor);
|
||||
pub(super) fn add(&mut self, id: BufferId, position: PanePosition) {
|
||||
self.map.insert(id, position);
|
||||
}
|
||||
|
||||
pub(super) fn remove(&mut self, id: BufferId) {
|
||||
@@ -26,14 +27,8 @@ impl PaneLayout {
|
||||
// TODO this needs to be ordered
|
||||
self.map
|
||||
.iter()
|
||||
.find(|(_, item)| matches!(item, PaneDescriptor::Floating))
|
||||
.find(|(_, item)| matches!(item, PanePosition::Floating))
|
||||
.map(|(id, _)| id)
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PaneDescriptor {
|
||||
Main { tab: u16 },
|
||||
Floating,
|
||||
}
|
||||
|
||||
+1
-7
@@ -11,7 +11,7 @@ use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
core::{
|
||||
buffer::{Buffer, BufferId, FinderBuffer, TextBuffer},
|
||||
buffer::{Buffer, BufferId, TextBuffer},
|
||||
EditorCore,
|
||||
},
|
||||
tui::render::styles::{cursor_style, line_number_style},
|
||||
@@ -60,11 +60,6 @@ impl TerminalState {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_finder(&mut self, _frame: &mut Frame, _outer_rect: Rect, _buffer: &FinderBuffer) {
|
||||
let _title = "FINDER";
|
||||
//TODO
|
||||
}
|
||||
|
||||
fn render_popup(&mut self, frame: &mut Frame, outer_rect: Rect, buffer: &TextBuffer) {
|
||||
let title = buffer.title().unwrap_or("<no title>");
|
||||
|
||||
@@ -106,7 +101,6 @@ impl TerminalState {
|
||||
let buf = core.get_buffer_by_id(floating);
|
||||
match buf {
|
||||
Some(Buffer::Text(floating)) => self.render_popup(frame, rect, floating),
|
||||
Some(Buffer::Finder(finder)) => self.render_finder(frame, rect, finder),
|
||||
None => {
|
||||
warn!(id=?floating, "Buffer not found");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user