Skip to content

Commit

Permalink
WIP: Initial tabs implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
nrabulinski committed Jul 1, 2023
1 parent a9849eb commit a3abe82
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 54 deletions.
4 changes: 2 additions & 2 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ impl Application {
// NOTE: this isn't necessarily true anymore. If
// `--vsplit` or `--hsplit` are used, the file which is
// 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 pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true));
doc.set_selection(view_id, pos);
Expand Down Expand Up @@ -383,7 +383,7 @@ impl Application {

// reset view position in case softwrap was enabled/disabled
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];
view.ensure_cursor_in_view(doc, scrolloff)
}
Expand Down
61 changes: 54 additions & 7 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use helix_view::{
keyboard::KeyCode,
tree,
view::View,
Document, DocumentId, Editor, ViewId,
Document, DocumentId, Editor, TabId, ViewId,
};

use anyhow::{anyhow, bail, ensure, Context as _};
Expand Down Expand Up @@ -307,6 +307,7 @@ impl MappableCommand {
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",
code_action, "Perform code action",
tab_picker, "Open tab picker",
buffer_picker, "Open buffer picker",
jumplist_picker, "Open jumplist picker",
symbol_picker, "Open symbol picker",
Expand Down Expand Up @@ -2523,6 +2524,50 @@ fn file_picker_in_current_directory(cx: &mut Context) {
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) {
let current = view!(cx.editor).doc;

Expand Down Expand Up @@ -2628,7 +2673,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() {
let doc = doc_mut!(cx.editor, doc_id);
view.sync_changes(doc);
Expand Down Expand Up @@ -2656,7 +2701,8 @@ fn jumplist_picker(cx: &mut Context) {

let picker = Picker::new(
cx.editor
.tree
.tabs
.curr_tree()
.views()
.flat_map(|(view, _)| {
view.jumps
Expand Down Expand Up @@ -2742,7 +2788,7 @@ pub fn command_palette(cx: &mut Context) {

command.execute(&mut ctx);

if ctx.editor.tree.contains(focus) {
if ctx.editor.tabs.curr_tree().contains(focus) {
let config = ctx.editor.config();
let mode = ctx.editor.mode();
let view = view_mut!(ctx.editor, focus);
Expand Down Expand Up @@ -2869,7 +2915,7 @@ async fn make_format_callback(
let format = format.await;

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;
}

Expand Down Expand Up @@ -4787,7 +4833,7 @@ fn vsplit_new(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) {
cx.editor.set_error(err.to_string());
return;
Expand All @@ -4801,7 +4847,8 @@ fn wclose(cx: &mut Context) {
fn wonly(cx: &mut Context) {
let views = cx
.editor
.tree
.tabs
.curr_tree()
.views()
.map(|(v, focus)| (v.id, focus))
.collect::<Vec<_>>();
Expand Down
6 changes: 4 additions & 2 deletions helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1511,7 +1511,7 @@ pub fn compute_inlay_hints_for_all_views(editor: &mut Editor, jobs: &mut crate::
return;
}

for (view, _) in editor.tree.views() {
for (view, _) in editor.tabs.curr_tree().views() {
let doc = match editor.documents.get(&view.doc) {
Some(doc) => doc,
None => continue,
Expand Down Expand Up @@ -1576,7 +1576,9 @@ fn compute_inlay_hints_for_view(
language_server.text_document_range_inlay_hints(doc.identifier(), range, None)?,
move |editor, _compositor, response: Option<Vec<lsp::InlayHint>>| {
// 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;
}

Expand Down
62 changes: 60 additions & 2 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fn quit(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
ensure!(args.is_empty(), ":quit takes no arguments");

// 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)?
}

Expand Down Expand Up @@ -770,7 +770,13 @@ fn quit_all_impl(cx: &mut compositor::Context, force: bool) -> anyhow::Result<()
}

// 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 {
cx.editor.close(view_id);
}
Expand Down Expand Up @@ -1516,6 +1522,43 @@ fn tree_sitter_scopes(
Ok(())
}

fn tabnext(
cx: &mut compositor::Context,
_args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.tabs.focus_next();
Ok(())
}

fn tabnew(
cx: &mut compositor::Context,
_args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}

// let id = view!(cx.editor).doc;

// if args.is_empty() {
cx.editor.tabs.new_tab();
cx.editor.new_file(Action::VerticalSplit);
// cx.editor.switch(id, Action::NewTab);
// } else {
// for arg in args {
// cx.editor
// .open(&PathBuf::from(arg.as_ref()), Action::VerticalSplit)?;
// }
// }

Ok(())
}

fn vsplit(
cx: &mut compositor::Context,
args: &[Cow<str>],
Expand Down Expand Up @@ -2675,6 +2718,21 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: debug_eval,
signature: CommandSignature::none(),
},
TypableCommand {
name: "tab-new",
aliases: &[],
doc: "Open a new tab.",
fun: tabnew,
signature: CommandSignature::none(),
// signature: CommandSignature::all(completers::filename)
},
TypableCommand {
name: "tab-next",
aliases: &[],
doc: "Focus next tab",
fun: tabnext,
signature: CommandSignature::none(),
},
TypableCommand {
name: "vsplit",
aliases: &["vs"],
Expand Down
1 change: 1 addition & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"space" => { "Space"
"f" => file_picker,
"F" => file_picker_in_current_directory,
"t" => tab_picker,
"b" => buffer_picker,
"j" => jumplist_picker,
"s" => symbol_picker,
Expand Down
59 changes: 51 additions & 8 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,49 @@ impl EditorView {
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
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
Expand Down Expand Up @@ -1044,7 +1087,7 @@ impl EditorView {
} = *event;

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(
&editor.documents[&view.doc],
row,
Expand All @@ -1056,7 +1099,7 @@ impl EditorView {
};

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)
.map(|coords| (coords, view.id))
})
Expand Down Expand Up @@ -1122,7 +1165,7 @@ impl EditorView {
}

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 {
MouseEventKind::ScrollUp => Direction::Backward,
Expand All @@ -1131,14 +1174,14 @@ impl EditorView {
};

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),
}

let offset = config.scroll_lines.unsigned_abs();
commands::scroll(cxt, offset, direction);

cxt.editor.tree.focus = current_view;
cxt.editor.tabs.curr_tree_mut().focus = current_view;
cxt.editor.ensure_cursor_in_view(current_view);

EventResult::Consumed(None)
Expand Down Expand Up @@ -1349,7 +1392,7 @@ impl Component for EditorView {
}

// if the focused view still exists and wasn't closed
if cx.editor.tree.contains(focus) {
if cx.editor.tabs.curr_tree().contains(focus) {
let config = cx.editor.config();
let mode = cx.editor.mode();
let view = view_mut!(cx.editor, focus);
Expand Down Expand Up @@ -1404,10 +1447,10 @@ impl Component for EditorView {
cx.editor.resize(editor_area);

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();
self.render_view(cx.editor, doc, view, area, surface, is_focused);
}
Expand Down
Loading

0 comments on commit a3abe82

Please sign in to comment.