mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 08:58:21 +00:00
parent
92b1b4c2a8
commit
94bbf3c9fb
10 changed files with 449 additions and 8 deletions
|
|
@ -64,7 +64,8 @@
|
|||
log_tag_commit: ( code: Char('t'), modifiers: ( bits: 0,),),
|
||||
commit_amend: ( code: Char('A'), modifiers: ( bits: 1,),),
|
||||
copy: ( code: Char('y'), modifiers: ( bits: 0,),),
|
||||
create_branch: ( code: Char('b'), modifiers: ( bits: 0,),),
|
||||
create_branch: ( code: Char('c'), modifiers: ( bits: 0,),),
|
||||
select_branch: ( code: Char('b'), modifiers: ( bits: 0,),),
|
||||
push: ( code: Char('p'), modifiers: ( bits: 0,),),
|
||||
fetch: ( code: Char('f'), modifiers: ( bits: 0,),),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::{
|
|||
error::{Error, Result},
|
||||
sync::utils,
|
||||
};
|
||||
use git2::BranchType;
|
||||
use scopetime::scope_time;
|
||||
use utils::get_head_repo;
|
||||
|
||||
|
|
@ -28,6 +29,86 @@ pub(crate) fn get_branch_name(repo_path: &str) -> Result<String> {
|
|||
Err(Error::NoHead)
|
||||
}
|
||||
|
||||
///
|
||||
pub struct BranchForDisplay {
|
||||
///
|
||||
pub name: String,
|
||||
///
|
||||
pub reference: String,
|
||||
///
|
||||
pub top_commit_message: String,
|
||||
///
|
||||
pub top_commit_reference: String,
|
||||
///
|
||||
pub is_head: bool,
|
||||
}
|
||||
|
||||
/// TODO make this cached
|
||||
/// Used to return only the nessessary information for displaying a branch
|
||||
/// rather than an iterator over the actual branches
|
||||
pub fn get_branches_to_display(
|
||||
repo_path: &str,
|
||||
) -> Result<Vec<BranchForDisplay>> {
|
||||
scope_time!("get_branches_to_display");
|
||||
let cur_repo = utils::repo(repo_path)?;
|
||||
let mut branches_for_display = vec![];
|
||||
|
||||
for b in cur_repo.branches(Some(BranchType::Local))? {
|
||||
let branch = &b?.0;
|
||||
let top_commit = branch.get().peel_to_commit()?;
|
||||
let mut commit_id = top_commit.id().to_string();
|
||||
commit_id.truncate(7);
|
||||
|
||||
branches_for_display.push(BranchForDisplay {
|
||||
name: String::from_utf8(Vec::from(branch.name_bytes()?))?,
|
||||
reference: String::from_utf8(Vec::from(
|
||||
branch.get().name_bytes(),
|
||||
))?,
|
||||
top_commit_message: String::from_utf8(Vec::from(
|
||||
top_commit.summary_bytes().unwrap_or(&[]),
|
||||
))?,
|
||||
top_commit_reference: commit_id,
|
||||
is_head: branch.is_head(),
|
||||
})
|
||||
}
|
||||
Ok(branches_for_display)
|
||||
}
|
||||
|
||||
/// Modify HEAD to point to a branch then checkout head, does not work if there are uncommitted changes
|
||||
pub fn checkout_branch(
|
||||
repo_path: &str,
|
||||
branch_ref: &str,
|
||||
) -> Result<()> {
|
||||
scope_time!("checkout_branch");
|
||||
// This defaults to a safe checkout, so don't delete anything that
|
||||
// hasn't been committed or stashed, in this case it will Err
|
||||
let repo = utils::repo(repo_path)?;
|
||||
let cur_ref = repo.head()?;
|
||||
if repo
|
||||
.statuses(Some(
|
||||
git2::StatusOptions::new().include_ignored(false),
|
||||
))?
|
||||
.is_empty()
|
||||
{
|
||||
repo.set_head(branch_ref)?;
|
||||
|
||||
if let Err(e) = repo.checkout_head(Some(
|
||||
git2::build::CheckoutBuilder::new().force(),
|
||||
)) {
|
||||
// This is safe beacuse cur_ref was just found
|
||||
repo.set_head(cur_ref.name().unwrap_or(""))?;
|
||||
return Err(Error::Git(e));
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Generic(
|
||||
format!("Cannot change branch. There are unstaged/staged changes which have not been committed/stashed. There is {:?} changes preventing checking out a different branch.", repo.statuses(Some(
|
||||
git2::StatusOptions::new().include_ignored(false),
|
||||
))?.len()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// creates a new branch pointing to current HEAD commit and updating HEAD to new branch
|
||||
pub fn create_branch(repo_path: &str, name: &str) -> Result<()> {
|
||||
scope_time!("create_branch");
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ pub mod status;
|
|||
mod tags;
|
||||
pub mod utils;
|
||||
|
||||
pub use branch::create_branch;
|
||||
pub(crate) use branch::get_branch_name;
|
||||
pub use branch::{
|
||||
checkout_branch, create_branch, get_branches_to_display,
|
||||
BranchForDisplay,
|
||||
};
|
||||
pub use commit::{amend, commit, tag};
|
||||
pub use commit_details::{
|
||||
get_commit_details, CommitDetails, CommitMessage,
|
||||
|
|
|
|||
15
src/app.rs
15
src/app.rs
|
|
@ -6,7 +6,8 @@ use crate::{
|
|||
Component, CreateBranchComponent, DrawableComponent,
|
||||
ExternalEditorComponent, HelpComponent,
|
||||
InspectCommitComponent, MsgComponent, PushComponent,
|
||||
ResetComponent, StashMsgComponent, TagCommitComponent,
|
||||
ResetComponent, SelectBranchComponent, StashMsgComponent,
|
||||
TagCommitComponent,
|
||||
},
|
||||
input::{Input, InputEvent, InputState},
|
||||
keys::{KeyConfig, SharedKeyConfig},
|
||||
|
|
@ -45,6 +46,7 @@ pub struct App {
|
|||
push_popup: PushComponent,
|
||||
tag_commit_popup: TagCommitComponent,
|
||||
create_branch_popup: CreateBranchComponent,
|
||||
select_branch_popup: SelectBranchComponent,
|
||||
cmdbar: RefCell<CommandBar>,
|
||||
tab: usize,
|
||||
revlog: Revlog,
|
||||
|
|
@ -116,6 +118,11 @@ impl App {
|
|||
theme.clone(),
|
||||
key_config.clone(),
|
||||
),
|
||||
select_branch_popup: SelectBranchComponent::new(
|
||||
queue.clone(),
|
||||
theme.clone(),
|
||||
key_config.clone(),
|
||||
),
|
||||
do_quit: false,
|
||||
cmdbar: RefCell::new(CommandBar::new(
|
||||
theme.clone(),
|
||||
|
|
@ -335,6 +342,7 @@ impl App {
|
|||
push_popup,
|
||||
tag_commit_popup,
|
||||
create_branch_popup,
|
||||
select_branch_popup,
|
||||
help,
|
||||
revlog,
|
||||
status_tab,
|
||||
|
|
@ -487,6 +495,9 @@ impl App {
|
|||
InternalEvent::CreateBranch => {
|
||||
self.create_branch_popup.open()?;
|
||||
}
|
||||
InternalEvent::SelectBranch => {
|
||||
self.select_branch_popup.open()?;
|
||||
}
|
||||
InternalEvent::TabSwitch => self.set_tab(0)?,
|
||||
InternalEvent::InspectCommit(id, tags) => {
|
||||
self.inspect_commit_popup.open(id, tags)?;
|
||||
|
|
@ -562,6 +573,7 @@ impl App {
|
|||
|| self.tag_commit_popup.is_visible()
|
||||
|| self.create_branch_popup.is_visible()
|
||||
|| self.push_popup.is_visible()
|
||||
|| self.select_branch_popup.is_visible()
|
||||
}
|
||||
|
||||
fn draw_popups<B: Backend>(
|
||||
|
|
@ -587,6 +599,7 @@ impl App {
|
|||
self.msg.draw(f, size)?;
|
||||
self.external_editor_popup.draw(f, size)?;
|
||||
self.tag_commit_popup.draw(f, size)?;
|
||||
self.select_branch_popup.draw(f, size)?;
|
||||
self.create_branch_popup.draw(f, size)?;
|
||||
self.push_popup.draw(f, size)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ mod inspect_commit;
|
|||
mod msg;
|
||||
mod push;
|
||||
mod reset;
|
||||
mod select_branch;
|
||||
mod stashmsg;
|
||||
mod tag_commit;
|
||||
mod textinput;
|
||||
|
|
@ -34,6 +35,7 @@ pub use inspect_commit::InspectCommitComponent;
|
|||
pub use msg::MsgComponent;
|
||||
pub use push::PushComponent;
|
||||
pub use reset::ResetComponent;
|
||||
pub use select_branch::SelectBranchComponent;
|
||||
pub use stashmsg::StashMsgComponent;
|
||||
pub use tag_commit::TagCommitComponent;
|
||||
pub use textinput::TextInputComponent;
|
||||
|
|
|
|||
321
src/components/select_branch.rs
Normal file
321
src/components/select_branch.rs
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
use super::{
|
||||
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
||||
DrawableComponent,
|
||||
};
|
||||
use crate::{
|
||||
keys::SharedKeyConfig,
|
||||
queue::{InternalEvent, NeedsUpdate, Queue},
|
||||
strings, ui,
|
||||
};
|
||||
use asyncgit::{
|
||||
sync::{
|
||||
checkout_branch, get_branches_to_display, BranchForDisplay,
|
||||
},
|
||||
CWD,
|
||||
};
|
||||
use crossterm::event::Event;
|
||||
use std::{cmp, convert::TryFrom};
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Block, BorderType, Borders, Clear, Paragraph},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use ui::style::SharedTheme;
|
||||
|
||||
///
|
||||
pub struct SelectBranchComponent {
|
||||
branch_names: Vec<BranchForDisplay>,
|
||||
visible: bool,
|
||||
selection: u16,
|
||||
queue: Queue,
|
||||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
}
|
||||
|
||||
impl DrawableComponent for SelectBranchComponent {
|
||||
fn draw<B: Backend>(
|
||||
&self,
|
||||
f: &mut Frame<B>,
|
||||
_rect: Rect,
|
||||
) -> Result<()> {
|
||||
// Render a scrolllist of branches inside a box
|
||||
|
||||
if self.visible {
|
||||
const SIZE: (u16, u16) = (50, 45);
|
||||
let scroll_threshold = SIZE.1 / 3;
|
||||
let scroll =
|
||||
self.selection.saturating_sub(scroll_threshold);
|
||||
|
||||
let area =
|
||||
ui::centered_rect_absolute(SIZE.0, SIZE.1, f.size());
|
||||
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(
|
||||
Block::default()
|
||||
.title(strings::SELECT_BRANCH_POPUP_MSG)
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Thick),
|
||||
area,
|
||||
);
|
||||
|
||||
let chunk = Layout::default()
|
||||
.vertical_margin(1)
|
||||
.horizontal_margin(1)
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[Constraint::Min(1), Constraint::Length(1)]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(area)[0];
|
||||
f.render_widget(
|
||||
Paragraph::new(
|
||||
self.get_text(&self.theme, area.width)?,
|
||||
)
|
||||
.scroll((scroll, 0))
|
||||
.alignment(Alignment::Left),
|
||||
chunk,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SelectBranchComponent {
|
||||
fn commands(
|
||||
&self,
|
||||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
if self.visible || force_all {
|
||||
out.clear();
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::scroll(&self.key_config),
|
||||
true,
|
||||
true,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::close_popup(&self.key_config),
|
||||
true,
|
||||
true,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::open_branch_create_popup(
|
||||
&self.key_config,
|
||||
),
|
||||
true,
|
||||
true,
|
||||
));
|
||||
}
|
||||
visibility_blocking(self)
|
||||
}
|
||||
|
||||
fn event(&mut self, ev: Event) -> Result<bool> {
|
||||
if self.visible {
|
||||
if let Event::Key(e) = ev {
|
||||
if e == self.key_config.exit_popup {
|
||||
self.hide()
|
||||
} else if e == self.key_config.move_down {
|
||||
self.move_selection(true)
|
||||
} else if e == self.key_config.move_up {
|
||||
self.move_selection(false)
|
||||
} else if e == self.key_config.enter {
|
||||
if let Err(e) = self.switch_to_selected_branch() {
|
||||
log::error!("switch branch error: {}", e);
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"switch branch error:\n{}",
|
||||
e
|
||||
)),
|
||||
);
|
||||
}
|
||||
self.hide()
|
||||
} else if e == self.key_config.create_branch {
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::CreateBranch);
|
||||
self.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectBranchComponent {
|
||||
pub fn new(
|
||||
queue: Queue,
|
||||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
branch_names: Vec::new(),
|
||||
visible: false,
|
||||
selection: 0,
|
||||
queue,
|
||||
theme,
|
||||
key_config,
|
||||
}
|
||||
}
|
||||
/// Get all the names of the branches in the repo
|
||||
pub fn get_branch_names() -> Result<Vec<BranchForDisplay>> {
|
||||
get_branches_to_display(CWD).map_err(anyhow::Error::new)
|
||||
}
|
||||
|
||||
///
|
||||
pub fn open(&mut self) -> Result<()> {
|
||||
self.update_branches()?;
|
||||
self.show()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
////
|
||||
pub fn update_branches(&mut self) -> Result<()> {
|
||||
self.branch_names = Self::get_branch_names()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
fn move_selection(&mut self, inc: bool) {
|
||||
let mut new_selection = self.selection;
|
||||
|
||||
new_selection = if inc {
|
||||
new_selection.saturating_add(1)
|
||||
} else {
|
||||
new_selection.saturating_sub(1)
|
||||
};
|
||||
new_selection = cmp::max(new_selection, 0);
|
||||
|
||||
if let Ok(max) =
|
||||
u16::try_from(self.branch_names.len().saturating_sub(1))
|
||||
{
|
||||
self.selection = cmp::min(new_selection, max);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get branches to display
|
||||
fn get_text(
|
||||
&self,
|
||||
theme: &SharedTheme,
|
||||
width_available: u16,
|
||||
) -> Result<Text> {
|
||||
const BRANCH_NAME_LENGTH: usize = 15;
|
||||
// total width - commit hash - branch name -"* " - "..." = remaining width
|
||||
let commit_message_length: usize =
|
||||
width_available as usize - 8 - BRANCH_NAME_LENGTH - 3 - 3;
|
||||
let mut txt = Vec::new();
|
||||
|
||||
for (i, displaybranch) in self.branch_names.iter().enumerate()
|
||||
{
|
||||
let mut commit_message =
|
||||
displaybranch.top_commit_message.clone();
|
||||
if commit_message.len() > commit_message_length {
|
||||
commit_message.truncate(commit_message_length - 3);
|
||||
commit_message += "...";
|
||||
}
|
||||
|
||||
let mut branch_name = displaybranch.name.clone();
|
||||
if branch_name.len() > BRANCH_NAME_LENGTH {
|
||||
branch_name.truncate(BRANCH_NAME_LENGTH - 3);
|
||||
branch_name += "...";
|
||||
}
|
||||
|
||||
let is_head_str =
|
||||
if displaybranch.is_head { "*" } else { " " };
|
||||
|
||||
txt.push(Spans::from(if self.selection as usize == i {
|
||||
vec![
|
||||
Span::styled(
|
||||
format!("{} ", is_head_str),
|
||||
theme.commit_author(true),
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
">{:w$} ",
|
||||
branch_name,
|
||||
w = BRANCH_NAME_LENGTH
|
||||
),
|
||||
theme.commit_author(true),
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{} ",
|
||||
displaybranch.top_commit_reference
|
||||
),
|
||||
theme.commit_hash(true),
|
||||
),
|
||||
Span::styled(
|
||||
commit_message.to_string(),
|
||||
theme.text(true, true),
|
||||
),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
Span::styled(
|
||||
format!("{} ", is_head_str),
|
||||
theme.commit_author(false),
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
" {:w$} ",
|
||||
branch_name,
|
||||
w = BRANCH_NAME_LENGTH
|
||||
),
|
||||
theme.commit_author(false),
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{} ",
|
||||
displaybranch.top_commit_reference
|
||||
),
|
||||
theme.commit_hash(false),
|
||||
),
|
||||
Span::styled(
|
||||
commit_message.to_string(),
|
||||
theme.text(true, false),
|
||||
),
|
||||
]
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(Text::from(txt))
|
||||
}
|
||||
|
||||
///
|
||||
fn switch_to_selected_branch(&self) -> Result<()> {
|
||||
checkout_branch(
|
||||
asyncgit::CWD,
|
||||
&self.branch_names[self.selection as usize].reference,
|
||||
)?;
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::Update(NeedsUpdate::ALL));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +60,7 @@ pub struct KeyConfig {
|
|||
pub commit_amend: KeyEvent,
|
||||
pub copy: KeyEvent,
|
||||
pub create_branch: KeyEvent,
|
||||
pub select_branch: KeyEvent,
|
||||
pub push: KeyEvent,
|
||||
pub fetch: KeyEvent,
|
||||
}
|
||||
|
|
@ -110,7 +111,8 @@ impl Default for KeyConfig {
|
|||
log_tag_commit: KeyEvent { code: KeyCode::Char('t'), modifiers: KeyModifiers::empty()},
|
||||
commit_amend: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::CONTROL},
|
||||
copy: KeyEvent { code: KeyCode::Char('y'), modifiers: KeyModifiers::empty()},
|
||||
create_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()},
|
||||
create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::NONE},
|
||||
select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::NONE},
|
||||
push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()},
|
||||
fetch: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ pub enum InternalEvent {
|
|||
///
|
||||
CreateBranch,
|
||||
///
|
||||
SelectBranch,
|
||||
///
|
||||
OpenExternalEditor(Option<String>),
|
||||
///
|
||||
Push(String),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ pub static PUSH_POPUP_STATES_ADDING: &str = "adding objects (1/3)";
|
|||
pub static PUSH_POPUP_STATES_DELTAS: &str = "deltas (2/3)";
|
||||
pub static PUSH_POPUP_STATES_PUSHING: &str = "pushing (3/3)";
|
||||
|
||||
pub static SELECT_BRANCH_POPUP_MSG: &str = "Switch Branch";
|
||||
|
||||
pub fn title_status(key_config: &SharedKeyConfig) -> String {
|
||||
format!(
|
||||
"Unstaged Changes [{}]",
|
||||
|
|
@ -595,13 +597,26 @@ pub mod commands {
|
|||
) -> CommandText {
|
||||
CommandText::new(
|
||||
format!(
|
||||
"Branch [{}]",
|
||||
"Create [{}]",
|
||||
get_hint(key_config.create_branch),
|
||||
),
|
||||
"open create branch popup",
|
||||
CMD_GROUP_GENERAL,
|
||||
)
|
||||
}
|
||||
pub fn open_branch_select_popup(
|
||||
key_config: &SharedKeyConfig,
|
||||
) -> CommandText {
|
||||
CommandText::new(
|
||||
format!(
|
||||
"Checkout [{}]",
|
||||
get_hint(key_config.select_branch),
|
||||
),
|
||||
"open select branch popup",
|
||||
CMD_GROUP_GENERAL,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn status_push(key_config: &SharedKeyConfig) -> CommandText {
|
||||
CommandText::new(
|
||||
format!("Push [{}]", get_hint(key_config.push),),
|
||||
|
|
|
|||
|
|
@ -396,12 +396,13 @@ impl Component for Status {
|
|||
}
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::open_branch_create_popup(
|
||||
strings::commands::open_branch_select_popup(
|
||||
&self.key_config,
|
||||
),
|
||||
true,
|
||||
true,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::status_push(&self.key_config),
|
||||
self.index_wd.branch_name().is_some(),
|
||||
|
|
@ -484,10 +485,10 @@ impl Component for Status {
|
|||
&& !self.index_wd.is_empty()
|
||||
{
|
||||
self.switch_focus(Focus::WorkDir)
|
||||
} else if k == self.key_config.create_branch {
|
||||
} else if k == self.key_config.select_branch {
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::CreateBranch);
|
||||
.push_back(InternalEvent::SelectBranch);
|
||||
Ok(true)
|
||||
} else if k == self.key_config.push {
|
||||
self.push();
|
||||
|
|
|
|||
Loading…
Reference in a new issue