Initial tabs implementation

pull/7109/head
Nikodem Rabuliński 2 years ago
parent 98b4df23a3
commit a767408e4c
No known key found for this signature in database

@ -193,7 +193,7 @@ impl Application {
// NOTE: this isn't necessarily true anymore. If // NOTE: this isn't necessarily true anymore. If
// `--vsplit` or `--hsplit` are used, the file which is // `--vsplit` or `--hsplit` are used, the file which is
// opened last is focused on. // opened last is focused on.
let view_id = editor.tree.focus; let view_id = editor.tabs.curr_tree().focus;
let doc = doc_mut!(editor, &doc_id); let doc = doc_mut!(editor, &doc_id);
let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true));
doc.set_selection(view_id, pos); doc.set_selection(view_id, pos);
@ -382,7 +382,7 @@ impl Application {
// reset view position in case softwrap was enabled/disabled // reset view position in case softwrap was enabled/disabled
let scrolloff = self.editor.config().scrolloff; let scrolloff = self.editor.config().scrolloff;
for (view, _) in self.editor.tree.views_mut() { for (view, _) in self.editor.tabs.curr_tree_mut().views_mut() {
let doc = &self.editor.documents[&view.doc]; let doc = &self.editor.documents[&view.doc];
view.ensure_cursor_in_view(doc, scrolloff) view.ensure_cursor_in_view(doc, scrolloff)
} }

@ -49,7 +49,7 @@ use helix_view::{
theme::Style, theme::Style,
tree, tree,
view::View, view::View,
Document, DocumentId, Editor, ViewId, Document, DocumentId, Editor, TabId, ViewId,
}; };
use anyhow::{anyhow, bail, ensure, Context as _}; use anyhow::{anyhow, bail, ensure, Context as _};
@ -329,6 +329,7 @@ impl MappableCommand {
file_picker_in_current_buffer_directory, "Open file picker at current buffers's directory", file_picker_in_current_buffer_directory, "Open file picker at current buffers's directory",
file_picker_in_current_directory, "Open file picker at current working directory", file_picker_in_current_directory, "Open file picker at current working directory",
code_action, "Perform code action", code_action, "Perform code action",
tab_picker, "Open tab picker",
buffer_picker, "Open buffer picker", buffer_picker, "Open buffer picker",
jumplist_picker, "Open jumplist picker", jumplist_picker, "Open jumplist picker",
symbol_picker, "Open symbol picker", symbol_picker, "Open symbol picker",
@ -2877,6 +2878,50 @@ fn file_picker_in_current_directory(cx: &mut Context) {
cx.push_layer(Box::new(overlaid(picker))); cx.push_layer(Box::new(overlaid(picker)));
} }
fn tab_picker(cx: &mut Context) {
let current = cx.editor.tabs.focus;
#[derive(Debug)]
struct TabMeta {
idx: usize,
id: TabId,
name: String,
is_current: bool,
}
impl ui::menu::Item for TabMeta {
type Data = ();
fn format(&self, _data: &Self::Data) -> Row {
let mut flags = String::new();
if self.is_current {
flags.push('*');
}
Row::new([(self.idx + 1).to_string(), self.name.clone(), flags])
}
}
let opts = cx
.editor
.tabs
.iter_tabs()
.enumerate()
.map(|(idx, (id, tab))| TabMeta {
idx,
id,
name: tab.name.clone(),
is_current: id == current,
})
.collect();
let picker = Picker::new(opts, (), |cx, meta, _action| {
cx.editor.tabs.focus = meta.id;
});
cx.push_layer(Box::new(overlaid(picker)));
}
fn buffer_picker(cx: &mut Context) { fn buffer_picker(cx: &mut Context) {
let current = view!(cx.editor).doc; let current = view!(cx.editor).doc;
@ -2982,7 +3027,7 @@ fn jumplist_picker(cx: &mut Context) {
} }
} }
for (view, _) in cx.editor.tree.views_mut() { for (view, _) in cx.editor.tabs.curr_tree_mut().views_mut() {
for doc_id in view.jumps.iter().map(|e| e.0).collect::<Vec<_>>().iter() { for doc_id in view.jumps.iter().map(|e| e.0).collect::<Vec<_>>().iter() {
let doc = doc_mut!(cx.editor, doc_id); let doc = doc_mut!(cx.editor, doc_id);
view.sync_changes(doc); view.sync_changes(doc);
@ -3010,7 +3055,8 @@ fn jumplist_picker(cx: &mut Context) {
let picker = Picker::new( let picker = Picker::new(
cx.editor cx.editor
.tree .tabs
.curr_tree()
.views() .views()
.flat_map(|(view, _)| { .flat_map(|(view, _)| {
view.jumps view.jumps
@ -3187,7 +3233,7 @@ pub fn command_palette(cx: &mut Context) {
command.execute(&mut ctx); command.execute(&mut ctx);
if ctx.editor.tree.contains(focus) { if ctx.editor.tabs.curr_tree().contains(focus) {
let config = ctx.editor.config(); let config = ctx.editor.config();
let mode = ctx.editor.mode(); let mode = ctx.editor.mode();
let view = view_mut!(ctx.editor, focus); let view = view_mut!(ctx.editor, focus);
@ -3315,7 +3361,7 @@ async fn make_format_callback(
let format = format.await; let format = format.await;
let call: job::Callback = Callback::Editor(Box::new(move |editor| { let call: job::Callback = Callback::Editor(Box::new(move |editor| {
if !editor.documents.contains_key(&doc_id) || !editor.tree.contains(view_id) { if !editor.documents.contains_key(&doc_id) || !editor.tabs.curr_tree().contains(view_id) {
return; return;
} }
@ -5156,7 +5202,7 @@ fn vsplit_new(cx: &mut Context) {
} }
fn wclose(cx: &mut Context) { fn wclose(cx: &mut Context) {
if cx.editor.tree.views().count() == 1 { if cx.editor.tabs.curr_tree().views().count() == 1 {
if let Err(err) = typed::buffers_remaining_impl(cx.editor) { if let Err(err) = typed::buffers_remaining_impl(cx.editor) {
cx.editor.set_error(err.to_string()); cx.editor.set_error(err.to_string());
return; return;
@ -5170,7 +5216,8 @@ fn wclose(cx: &mut Context) {
fn wonly(cx: &mut Context) { fn wonly(cx: &mut Context) {
let views = cx let views = cx
.editor .editor
.tree .tabs
.curr_tree()
.views() .views()
.map(|(v, focus)| (v.id, focus)) .map(|(v, focus)| (v.id, focus))
.collect::<Vec<_>>(); .collect::<Vec<_>>();

@ -1174,7 +1174,7 @@ pub fn compute_inlay_hints_for_all_views(editor: &mut Editor, jobs: &mut crate::
return; return;
} }
for (view, _) in editor.tree.views() { for (view, _) in editor.tabs.curr_tree().views() {
let doc = match editor.documents.get(&view.doc) { let doc = match editor.documents.get(&view.doc) {
Some(doc) => doc, Some(doc) => doc,
None => continue, None => continue,
@ -1239,7 +1239,9 @@ fn compute_inlay_hints_for_view(
language_server.text_document_range_inlay_hints(doc.identifier(), range, None)?, language_server.text_document_range_inlay_hints(doc.identifier(), range, None)?,
move |editor, _compositor, response: Option<Vec<lsp::InlayHint>>| { move |editor, _compositor, response: Option<Vec<lsp::InlayHint>>| {
// The config was modified or the window was closed while the request was in flight // The config was modified or the window was closed while the request was in flight
if !editor.config().lsp.display_inlay_hints || editor.tree.try_get(view_id).is_none() { if !editor.config().lsp.display_inlay_hints
|| editor.tabs.curr_tree().try_get(view_id).is_none()
{
return; return;
} }

@ -76,7 +76,7 @@ fn quit(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
ensure!(args.is_empty(), ":quit takes no arguments"); ensure!(args.is_empty(), ":quit takes no arguments");
// last view and we have unsaved changes // last view and we have unsaved changes
if cx.editor.tree.views().count() == 1 { if cx.editor.tabs.curr_tree().views().count() == 1 {
buffers_remaining_impl(cx.editor)? buffers_remaining_impl(cx.editor)?
} }
@ -788,7 +788,13 @@ fn quit_all_impl(cx: &mut compositor::Context, force: bool) -> anyhow::Result<()
} }
// close all views // close all views
let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); let views: Vec<_> = cx
.editor
.tabs
.curr_tree()
.views()
.map(|(view, _)| view.id)
.collect();
for view_id in views { for view_id in views {
cx.editor.close(view_id); cx.editor.close(view_id);
} }
@ -1610,6 +1616,49 @@ fn tree_sitter_highlight_name(
Ok(()) Ok(())
} }
fn tab_next(
cx: &mut compositor::Context,
_args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.tabs.focus_next();
Ok(())
}
fn tab_previous(
cx: &mut compositor::Context,
_args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.tabs.focus_previous();
Ok(())
}
fn tab_new(
cx: &mut compositor::Context,
_args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.tabs.new_tab();
cx.editor.new_file(Action::VerticalSplit);
Ok(())
}
fn vsplit( fn vsplit(
cx: &mut compositor::Context, cx: &mut compositor::Context,
args: &[Cow<str>], args: &[Cow<str>],
@ -2905,6 +2954,27 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: debug_eval, fun: debug_eval,
signature: CommandSignature::none(), signature: CommandSignature::none(),
}, },
TypableCommand {
name: "tab-new",
aliases: &[],
doc: "Create a new tab.",
fun: tab_new,
signature: CommandSignature::none(),
},
TypableCommand {
name: "tab-next",
aliases: &[],
doc: "Goto next tab.",
fun: tab_next,
signature: CommandSignature::none(),
},
TypableCommand {
name: "tab-previous",
aliases: &[],
doc: "Goto previous tab.",
fun: tab_previous,
signature: CommandSignature::none(),
},
TypableCommand { TypableCommand {
name: "vsplit", name: "vsplit",
aliases: &["vs"], aliases: &["vs"],

@ -221,6 +221,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"space" => { "Space" "space" => { "Space"
"f" => file_picker, "f" => file_picker,
"F" => file_picker_in_current_directory, "F" => file_picker_in_current_directory,
"t" => tab_picker,
"b" => buffer_picker, "b" => buffer_picker,
"j" => jumplist_picker, "j" => jumplist_picker,
"s" => symbol_picker, "s" => symbol_picker,

@ -577,6 +577,49 @@ impl EditorView {
Vec::new() Vec::new()
} }
/// Render tabline at the top
pub fn render_tabline(editor: &Editor, viewport: Rect, surface: &mut Surface) {
surface.clear_with(
viewport,
editor
.theme
.try_get("ui.bufferline.background")
.unwrap_or_else(|| editor.theme.get("ui.statusline")),
);
let bufferline_active = editor
.theme
.try_get("ui.bufferline.active")
.unwrap_or_else(|| editor.theme.get("ui.statusline.active"));
let bufferline_inactive = editor
.theme
.try_get("ui.bufferline")
.unwrap_or_else(|| editor.theme.get("ui.statusline.inactive"));
let mut x = viewport.x;
let current_tab = editor.tabs.focus;
for (id, tab) in editor.tabs.iter_tabs() {
let style = if current_tab == id {
bufferline_active
} else {
bufferline_inactive
};
let text = format!(" {} ", tab.name);
let used_width = viewport.x.saturating_sub(x);
let rem_width = surface.area.width.saturating_sub(used_width);
x = surface
.set_stringn(x, viewport.y, text, rem_width as usize, style)
.0;
if x >= surface.area.right() {
break;
}
}
}
/// Render bufferline at the top /// Render bufferline at the top
pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) { pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) {
let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer
@ -1106,7 +1149,7 @@ impl EditorView {
} = *event; } = *event;
let pos_and_view = |editor: &Editor, row, column, ignore_virtual_text| { let pos_and_view = |editor: &Editor, row, column, ignore_virtual_text| {
editor.tree.views().find_map(|(view, _focus)| { editor.tabs.curr_tree().views().find_map(|(view, _focus)| {
view.pos_at_screen_coords( view.pos_at_screen_coords(
&editor.documents[&view.doc], &editor.documents[&view.doc],
row, row,
@ -1118,7 +1161,7 @@ impl EditorView {
}; };
let gutter_coords_and_view = |editor: &Editor, row, column| { let gutter_coords_and_view = |editor: &Editor, row, column| {
editor.tree.views().find_map(|(view, _focus)| { editor.tabs.curr_tree().views().find_map(|(view, _focus)| {
view.gutter_coords_at_screen_coords(row, column) view.gutter_coords_at_screen_coords(row, column)
.map(|coords| (coords, view.id)) .map(|coords| (coords, view.id))
}) })
@ -1198,7 +1241,7 @@ impl EditorView {
} }
MouseEventKind::ScrollUp | MouseEventKind::ScrollDown => { MouseEventKind::ScrollUp | MouseEventKind::ScrollDown => {
let current_view = cxt.editor.tree.focus; let current_view = cxt.editor.tabs.curr_tree().focus;
let direction = match event.kind { let direction = match event.kind {
MouseEventKind::ScrollUp => Direction::Backward, MouseEventKind::ScrollUp => Direction::Backward,
@ -1207,14 +1250,14 @@ impl EditorView {
}; };
match pos_and_view(cxt.editor, row, column, false) { match pos_and_view(cxt.editor, row, column, false) {
Some((_, view_id)) => cxt.editor.tree.focus = view_id, Some((_, view_id)) => cxt.editor.tabs.curr_tree_mut().focus = view_id,
None => return EventResult::Ignored(None), None => return EventResult::Ignored(None),
} }
let offset = config.scroll_lines.unsigned_abs(); let offset = config.scroll_lines.unsigned_abs();
commands::scroll(cxt, offset, direction, false); commands::scroll(cxt, offset, direction, false);
cxt.editor.tree.focus = current_view; cxt.editor.tabs.curr_tree_mut().focus = current_view;
cxt.editor.ensure_cursor_in_view(current_view); cxt.editor.ensure_cursor_in_view(current_view);
EventResult::Consumed(None) EventResult::Consumed(None)
@ -1486,10 +1529,10 @@ impl Component for EditorView {
cx.editor.resize(editor_area); cx.editor.resize(editor_area);
if use_bufferline { if use_bufferline {
Self::render_bufferline(cx.editor, area.with_height(1), surface); Self::render_tabline(cx.editor, area.with_height(1), surface);
} }
for (view, is_focused) in cx.editor.tree.views() { for (view, is_focused) in cx.editor.tabs.curr_tree().views() {
let doc = cx.editor.document(view.doc).unwrap(); let doc = cx.editor.document(view.doc).unwrap();
self.render_view(cx.editor, doc, view, area, surface, is_focused); self.render_view(cx.editor, doc, view, area, surface, is_focused);
} }

@ -6,8 +6,9 @@ use crate::{
info::Info, info::Info,
input::KeyEvent, input::KeyEvent,
register::Registers, register::Registers,
tabs::Tabs,
theme::{self, Theme}, theme::{self, Theme},
tree::{self, Tree}, tree::{self},
view::ViewPosition, view::ViewPosition,
Align, Document, DocumentId, View, ViewId, Align, Document, DocumentId, View, ViewId,
}; };
@ -944,7 +945,7 @@ use futures_util::stream::{Flatten, Once};
pub struct Editor { pub struct Editor {
/// Current editing mode. /// Current editing mode.
pub mode: Mode, pub mode: Mode,
pub tree: Tree, pub tabs: Tabs,
pub next_document_id: DocumentId, pub next_document_id: DocumentId,
pub documents: BTreeMap<DocumentId, Document>, pub documents: BTreeMap<DocumentId, Document>,
@ -1093,7 +1094,7 @@ impl Editor {
Self { Self {
mode: Mode::Normal, mode: Mode::Normal,
tree: Tree::new(area), tabs: Tabs::new(area),
next_document_id: DocumentId::default(), next_document_id: DocumentId::default(),
documents: BTreeMap::new(), documents: BTreeMap::new(),
saves: HashMap::new(), saves: HashMap::new(),
@ -1447,7 +1448,7 @@ impl Editor {
} }
} }
for (view, _) in self.tree.views_mut() { for (view, _) in self.tabs.curr_tree_mut().views_mut() {
let doc = doc_mut!(self, &view.doc); let doc = doc_mut!(self, &view.doc);
view.sync_changes(doc); view.sync_changes(doc);
view.gutters = config.gutters.clone(); view.gutters = config.gutters.clone();
@ -1456,7 +1457,7 @@ impl Editor {
} }
fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) { fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) {
let view = self.tree.get_mut(current_view); let view = self.tabs.curr_tree_mut().get_mut(current_view);
view.doc = doc_id; view.doc = doc_id;
view.offset = ViewPosition::default(); view.offset = ViewPosition::default();
@ -1481,6 +1482,7 @@ impl Editor {
match action { match action {
Action::Replace => { Action::Replace => {
let (view, doc) = current_ref!(self); let (view, doc) = current_ref!(self);
let view_id = view.id;
// If the current view is an empty scratch buffer and is not displayed in any other views, delete it. // If the current view is an empty scratch buffer and is not displayed in any other views, delete it.
// Boolean value is determined before the call to `view_mut` because the operation requires a borrow // Boolean value is determined before the call to `view_mut` because the operation requires a borrow
// of `self.tree`, which is mutably borrowed when `view_mut` is called. // of `self.tree`, which is mutably borrowed when `view_mut` is called.
@ -1491,9 +1493,10 @@ impl Editor {
&& id != doc.id && id != doc.id
// Ensure the buffer is not displayed in any other splits. // Ensure the buffer is not displayed in any other splits.
&& !self && !self
.tree .tabs
.curr_tree_mut()
.traverse() .traverse()
.any(|(_, v)| v.doc == doc.id && v.id != view.id); .any(|(_, v)| v.doc == doc.id && v.id != view_id);
let (view, doc) = current!(self); let (view, doc) = current!(self);
let view_id = view.id; let view_id = view.id;
@ -1508,7 +1511,7 @@ impl Editor {
self.documents.remove(&id); self.documents.remove(&id);
// Remove the scratch buffer from any jumplists // Remove the scratch buffer from any jumplists
for (view, _) in self.tree.views_mut() { for (view, _) in self.tabs.curr_tree_mut().views_mut() {
view.remove_document(&id); view.remove_document(&id);
} }
} else { } else {
@ -1539,13 +1542,15 @@ impl Editor {
} }
Action::HorizontalSplit | Action::VerticalSplit => { Action::HorizontalSplit | Action::VerticalSplit => {
// copy the current view, unless there is no view yet // copy the current view, unless there is no view yet
let focus = self.tabs.curr_tree().focus;
let view = self let view = self
.tree .tabs
.try_get(self.tree.focus) .curr_tree_mut()
.try_get(focus)
.filter(|v| id == v.doc) // Different Document .filter(|v| id == v.doc) // Different Document
.cloned() .cloned()
.unwrap_or_else(|| View::new(id, self.config().gutters.clone())); .unwrap_or_else(|| View::new(id, self.config().gutters.clone()));
let view_id = self.tree.split( let view_id = self.tabs.curr_tree_mut().split(
view, view,
match action { match action {
Action::HorizontalSplit => Layout::Horizontal, Action::HorizontalSplit => Layout::Horizontal,
@ -1644,15 +1649,17 @@ impl Editor {
Ok(id) Ok(id)
} }
// TODO(nrabulinski): Closing last view in a tab should move focus to previous tab
pub fn close(&mut self, id: ViewId) { pub fn close(&mut self, id: ViewId) {
// Remove selections for the closed view on all documents. // Remove selections for the closed view on all documents.
for doc in self.documents_mut() { for doc in self.documents_mut() {
doc.remove_view(id); doc.remove_view(id);
} }
self.tree.remove(id); self.tabs.curr_tree_mut().remove(id);
self._refresh(); self._refresh();
} }
// TODO(nrabulinski): Closing last view in a tab should move focus to previous tab
pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> { pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
let doc = match self.documents.get_mut(&doc_id) { let doc = match self.documents.get_mut(&doc_id) {
Some(doc) => doc, Some(doc) => doc,
@ -1676,7 +1683,8 @@ impl Editor {
} }
let actions: Vec<Action> = self let actions: Vec<Action> = self
.tree .tabs
.curr_tree_mut()
.views_mut() .views_mut()
.filter_map(|(view, _focus)| { .filter_map(|(view, _focus)| {
view.remove_document(&doc_id); view.remove_document(&doc_id);
@ -1711,7 +1719,7 @@ impl Editor {
// If the document we removed was visible in all views, we will have no more views. We don't // If the document we removed was visible in all views, we will have no more views. We don't
// want to close the editor just for a simple buffer close, so we need to create a new view // want to close the editor just for a simple buffer close, so we need to create a new view
// containing either an existing document, or a brand new document. // containing either an existing document, or a brand new document.
if self.tree.views().next().is_none() { if self.tabs.curr_tree().views().next().is_none() {
let doc_id = self let doc_id = self
.documents .documents
.iter() .iter()
@ -1719,7 +1727,7 @@ impl Editor {
.next() .next()
.unwrap_or_else(|| self.new_document(Document::default(self.config.clone()))); .unwrap_or_else(|| self.new_document(Document::default(self.config.clone())));
let view = View::new(doc_id, self.config().gutters.clone()); let view = View::new(doc_id, self.config().gutters.clone());
let view_id = self.tree.insert(view); let view_id = self.tabs.curr_tree_mut().insert(view);
let doc = doc_mut!(self, &doc_id); let doc = doc_mut!(self, &doc_id);
doc.ensure_view_init(view_id); doc.ensure_view_init(view_id);
doc.mark_as_focused(); doc.mark_as_focused();
@ -1768,13 +1776,17 @@ impl Editor {
} }
pub fn resize(&mut self, area: Rect) { pub fn resize(&mut self, area: Rect) {
if self.tree.resize(area) { if self
.tabs
.iter_tabs_mut()
.fold(false, |acc, (_, tab)| acc | tab.tree.resize(area))
{
self._refresh(); self._refresh();
}; }
} }
pub fn focus(&mut self, view_id: ViewId) { pub fn focus(&mut self, view_id: ViewId) {
let prev_id = std::mem::replace(&mut self.tree.focus, view_id); let prev_id = std::mem::replace(&mut self.tabs.curr_tree_mut().focus, view_id);
// if leaving the view: mode should reset and the cursor should be // if leaving the view: mode should reset and the cursor should be
// within view // within view
@ -1783,7 +1795,7 @@ impl Editor {
self.ensure_cursor_in_view(view_id); self.ensure_cursor_in_view(view_id);
// Update jumplist selections with new document changes. // Update jumplist selections with new document changes.
for (view, _focused) in self.tree.views_mut() { for (view, _focused) in self.tabs.curr_tree_mut().views_mut() {
let doc = doc_mut!(self, &view.doc); let doc = doc_mut!(self, &view.doc);
view.sync_changes(doc); view.sync_changes(doc);
} }
@ -1795,35 +1807,39 @@ impl Editor {
} }
pub fn focus_next(&mut self) { pub fn focus_next(&mut self) {
self.focus(self.tree.next()); self.focus(self.tabs.curr_tree().next());
} }
pub fn focus_prev(&mut self) { pub fn focus_prev(&mut self) {
self.focus(self.tree.prev()); self.focus(self.tabs.curr_tree().prev());
} }
pub fn focus_direction(&mut self, direction: tree::Direction) { pub fn focus_direction(&mut self, direction: tree::Direction) {
let current_view = self.tree.focus; let current_view = self.tabs.curr_tree().focus;
if let Some(id) = self.tree.find_split_in_direction(current_view, direction) { if let Some(id) = self
.tabs
.curr_tree_mut()
.find_split_in_direction(current_view, direction)
{
self.focus(id) self.focus(id)
} }
} }
pub fn swap_split_in_direction(&mut self, direction: tree::Direction) { pub fn swap_split_in_direction(&mut self, direction: tree::Direction) {
self.tree.swap_split_in_direction(direction); self.tabs.curr_tree_mut().swap_split_in_direction(direction);
} }
pub fn transpose_view(&mut self) { pub fn transpose_view(&mut self) {
self.tree.transpose(); self.tabs.curr_tree_mut().transpose();
} }
pub fn should_close(&self) -> bool { pub fn should_close(&self) -> bool {
self.tree.is_empty() self.tabs.curr_tree().is_empty()
} }
pub fn ensure_cursor_in_view(&mut self, id: ViewId) { pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
let config = self.config(); let config = self.config();
let view = self.tree.get_mut(id); let view = self.tabs.curr_tree_mut().get_mut(id);
let doc = &self.documents[&view.doc]; let doc = &self.documents[&view.doc];
view.ensure_cursor_in_view(doc, config.scrolloff) view.ensure_cursor_in_view(doc, config.scrolloff)
} }
@ -2075,7 +2091,7 @@ impl Editor {
current_view.id current_view.id
} else if let Some(view_id) = doc.selections().keys().next() { } else if let Some(view_id) = doc.selections().keys().next() {
let view_id = *view_id; let view_id = *view_id;
let view = self.tree.get_mut(view_id); let view = self.tabs.curr_tree_mut().get_mut(view_id);
view.sync_changes(doc); view.sync_changes(doc);
view_id view_id
} else { } else {

@ -13,6 +13,7 @@ pub mod info;
pub mod input; pub mod input;
pub mod keyboard; pub mod keyboard;
pub mod register; pub mod register;
pub mod tabs;
pub mod theme; pub mod theme;
pub mod tree; pub mod tree;
pub mod view; pub mod view;
@ -38,6 +39,7 @@ impl std::fmt::Display for DocumentId {
slotmap::new_key_type! { slotmap::new_key_type! {
pub struct ViewId; pub struct ViewId;
pub struct TabId;
} }
pub enum Align { pub enum Align {

@ -22,7 +22,8 @@ macro_rules! current {
#[macro_export] #[macro_export]
macro_rules! current_ref { macro_rules! current_ref {
($editor:expr) => {{ ($editor:expr) => {{
let view = $editor.tree.get($editor.tree.focus); let tree = $editor.tabs.curr_tree();
let view = tree.get(tree.focus);
let doc = &$editor.documents[&view.doc]; let doc = &$editor.documents[&view.doc];
(view, doc) (view, doc)
}}; }};
@ -45,10 +46,12 @@ macro_rules! doc_mut {
#[macro_export] #[macro_export]
macro_rules! view_mut { macro_rules! view_mut {
($editor:expr, $id:expr) => {{ ($editor:expr, $id:expr) => {{
$editor.tree.get_mut($id) let tree = $editor.tabs.curr_tree_mut();
tree.get_mut($id)
}}; }};
($editor:expr) => {{ ($editor:expr) => {{
$editor.tree.get_mut($editor.tree.focus) let tree = $editor.tabs.curr_tree_mut();
tree.get_mut(tree.focus)
}}; }};
} }
@ -57,10 +60,12 @@ macro_rules! view_mut {
#[macro_export] #[macro_export]
macro_rules! view { macro_rules! view {
($editor:expr, $id:expr) => {{ ($editor:expr, $id:expr) => {{
$editor.tree.get($id) let tree = $editor.tabs.curr_tree();
tree.get($id)
}}; }};
($editor:expr) => {{ ($editor:expr) => {{
$editor.tree.get($editor.tree.focus) let tree = $editor.tabs.curr_tree();
tree.get(tree.focus)
}}; }};
} }

@ -0,0 +1,79 @@
use slotmap::HopSlotMap;
use crate::{graphics::Rect, tree::Tree, TabId};
#[derive(Debug)]
pub struct Tab {
pub name: String,
pub tree: Tree,
}
#[derive(Debug)]
pub struct Tabs {
pub focus: TabId,
tabs: HopSlotMap<TabId, Tab>,
}
impl Tabs {
#[inline]
pub fn new(area: Rect) -> Self {
let mut tabs = HopSlotMap::with_key();
let tab = Tab {
name: "Tab 0".to_string(),
tree: Tree::new(area),
};
let focus = tabs.insert(tab);
Self { focus, tabs }
}
#[inline]
pub fn curr_tree_mut(&mut self) -> &mut Tree {
&mut self.tabs.get_mut(self.focus).unwrap().tree
}
#[inline]
pub fn curr_tree(&self) -> &Tree {
&self.tabs.get(self.focus).unwrap().tree
}
#[inline]
pub fn iter_tabs_mut(&mut self) -> impl Iterator<Item = (TabId, &mut Tab)> {
self.tabs.iter_mut()
}
#[inline]
pub fn iter_tabs(&self) -> impl Iterator<Item = (TabId, &Tab)> {
self.tabs.iter()
}
#[inline]
pub fn new_tab(&mut self) -> TabId {
let area = self.curr_tree().area();
let new_tab = Tab {
name: format!("Tab {}", self.tabs.len()),
tree: Tree::new(area),
};
let new_focus = self.tabs.insert(new_tab);
self.focus = new_focus;
new_focus
}
#[inline]
pub fn focus_next(&mut self) -> TabId {
let curr = self.focus;
let mut iter = self.tabs.keys().skip_while(|id| *id != curr);
iter.next();
let id = iter.next().or_else(|| self.tabs.keys().next()).unwrap();
self.focus = id;
id
}
#[inline]
pub fn focus_previous(&mut self) -> TabId {
let curr = self.focus;
let iter = self.tabs.keys().take_while(|id| *id != curr);
let id = iter.last().or_else(|| self.tabs.keys().last()).unwrap();
self.focus = id;
id
}
}
Loading…
Cancel
Save