use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::time::{Instant, SystemTime};
use hjkl_buffer::Buffer;
use hjkl_engine::{Editor, VimMode};
use crate::host::TuiHost;
use crate::syntax::BufferId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MouseFlags {
pub normal: bool,
pub visual: bool,
pub insert: bool,
pub command: bool,
pub help: bool,
}
impl MouseFlags {
pub fn all() -> Self {
Self {
normal: true,
visual: true,
insert: true,
command: true,
help: true,
}
}
pub fn none() -> Self {
Self {
normal: false,
visual: false,
insert: false,
command: false,
help: false,
}
}
pub fn from_flags(s: &str) -> Self {
if s == "a" {
return Self::all();
}
let mut f = Self::none();
for c in s.chars() {
match c {
'n' => f.normal = true,
'v' => f.visual = true,
'i' => f.insert = true,
'c' => f.command = true,
'h' => f.help = true,
'a' => {
return Self::all();
}
_ => {}
}
}
f
}
pub fn as_flags_str(&self) -> String {
if self.normal && self.visual && self.insert && self.command && self.help {
return "a".into();
}
let mut s = String::new();
if self.normal {
s.push('n');
}
if self.visual {
s.push('v');
}
if self.insert {
s.push('i');
}
if self.command {
s.push('c');
}
if self.help {
s.push('h');
}
s
}
}
impl Default for MouseFlags {
fn default() -> Self {
Self::all()
}
}
pub fn mouse_enabled_for(mode: VimMode, flags: &MouseFlags) -> bool {
match mode {
VimMode::Normal => flags.normal,
VimMode::Visual | VimMode::VisualLine | VimMode::VisualBlock => flags.visual,
VimMode::Insert => flags.insert,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiskState {
Synced,
ChangedOnDisk,
DeletedOnDisk,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DiagSeverity {
Error = 1,
Warning = 2,
Info = 3,
Hint = 4,
}
#[derive(Debug, Clone)]
pub struct LspDiag {
pub start_row: usize,
pub start_col: usize,
pub end_row: usize,
pub end_col: usize,
pub severity: DiagSeverity,
pub message: String,
pub source: Option<String>,
pub code: Option<String>,
}
pub struct LspServerInfo {
pub initialized: bool,
pub capabilities: serde_json::Value,
}
#[derive(Debug, Clone)]
pub enum LspPendingRequest {
GotoDefinition {
buffer_id: hjkl_lsp::BufferId,
origin: (usize, usize),
},
GotoDeclaration {
buffer_id: hjkl_lsp::BufferId,
origin: (usize, usize),
},
GotoTypeDefinition {
buffer_id: hjkl_lsp::BufferId,
origin: (usize, usize),
},
GotoImplementation {
buffer_id: hjkl_lsp::BufferId,
origin: (usize, usize),
},
GotoReferences {
buffer_id: hjkl_lsp::BufferId,
origin: (usize, usize),
},
Hover {
buffer_id: hjkl_lsp::BufferId,
origin: (usize, usize),
},
HoverAtMouse {
buffer_id: hjkl_lsp::BufferId,
origin: (usize, usize),
},
Completion {
buffer_id: hjkl_lsp::BufferId,
anchor_row: usize,
anchor_col: usize,
},
CodeAction {
buffer_id: hjkl_lsp::BufferId,
anchor_row: usize,
anchor_col: usize,
},
Rename {
buffer_id: hjkl_lsp::BufferId,
anchor_row: usize,
anchor_col: usize,
new_name: String,
},
Format {
buffer_id: hjkl_lsp::BufferId,
range: Option<(usize, usize, usize, usize)>,
},
}
fn buffer_signature(editor: &Editor<Buffer, TuiHost>) -> (u64, usize) {
let mut hasher = DefaultHasher::new();
let mut len = 0usize;
let lines = editor.buffer().lines();
for (i, l) in lines.iter().enumerate() {
if i > 0 {
b'\n'.hash(&mut hasher);
len += 1;
}
l.hash(&mut hasher);
len += l.len();
}
(hasher.finish(), len)
}
pub struct BufferSlot {
pub buffer_id: BufferId,
pub editor: Editor<Buffer, TuiHost>,
pub filename: Option<PathBuf>,
pub dirty: bool,
pub is_new_file: bool,
pub is_untracked: bool,
pub diag_signs: Vec<hjkl_buffer::Sign>,
pub diag_signs_lsp: Vec<hjkl_buffer::Sign>,
pub lsp_diags: Vec<LspDiag>,
pub(crate) last_lsp_dirty_gen: Option<u64>,
pub git_signs: Vec<hjkl_buffer::Sign>,
pub(super) last_git_dirty_gen: Option<u64>,
pub(super) last_git_refresh_at: Instant,
pub(super) last_recompute_at: Instant,
pub(super) last_recompute_key: Option<(u64, usize, usize)>,
pub(super) saved_hash: u64,
pub(super) saved_len: usize,
pub disk_mtime: Option<SystemTime>,
pub disk_len: Option<u64>,
pub disk_state: DiskState,
pub(crate) viewport_render_output: Option<crate::syntax::RenderOutput>,
pub(crate) top_render_output: Option<crate::syntax::RenderOutput>,
pub(crate) bottom_render_output: Option<crate::syntax::RenderOutput>,
pub(crate) dirty_rows_log: Vec<(u64, std::ops::RangeInclusive<usize>)>,
}
pub(crate) fn find_project_root(start: &std::path::Path) -> PathBuf {
const MARKERS: &[&str] = &[
".git",
"Cargo.toml",
"package.json",
"go.mod",
"pyproject.toml",
"setup.py",
"composer.json",
".hg",
];
let mut dir = start.to_owned();
loop {
for marker in MARKERS {
if dir.join(marker).exists() {
return dir;
}
}
match dir.parent() {
Some(p) => dir = p.to_owned(),
None => return start.to_owned(),
}
}
}
impl BufferSlot {
pub(super) fn snapshot_saved(&mut self) {
let (h, l) = buffer_signature(&self.editor);
self.saved_hash = h;
self.saved_len = l;
self.dirty = false;
}
pub(super) fn refresh_dirty_against_saved(&mut self) -> u128 {
let t = std::time::Instant::now();
let (h, l) = buffer_signature(&self.editor);
let elapsed = t.elapsed().as_micros();
self.dirty = h != self.saved_hash || l != self.saved_len;
elapsed
}
}