use std::hash::Hasher;
use std::path::PathBuf;
use std::time::{Instant, SystemTime};
use hjkl_buffer::Buffer;
use hjkl_buffer_tui::Sign;
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,
auto: bool,
},
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 rope = editor.buffer().rope();
let mut hasher = ahash::AHasher::default();
let mut len = 0usize;
for chunk in rope.chunks() {
let bytes = chunk.as_bytes();
hasher.write(bytes);
len += bytes.len();
}
(hasher.finish(), len)
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct BufferFeatures {
pub syntax: bool,
pub lsp: bool,
pub hover: bool,
pub end_of_buffer: bool,
}
impl Default for BufferFeatures {
fn default() -> Self {
Self {
syntax: true,
lsp: true,
hover: true,
end_of_buffer: true,
}
}
}
pub struct BufferSlot {
pub buffer_id: BufferId,
pub(crate) is_explorer: bool,
pub(crate) features: BufferFeatures,
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<Sign>,
pub diag_signs_lsp: Vec<Sign>,
pub lsp_diags: Vec<LspDiag>,
pub(crate) last_lsp_dirty_gen: Option<u64>,
pub git_signs: Vec<Sign>,
pub(super) last_git_dirty_gen: Option<u64>,
pub(super) last_git_refresh_at: Instant,
pub(super) saved_hash: u64,
pub(super) saved_len: usize,
pub(super) signature_cache: Option<(u64, (u64, usize))>,
pub disk_mtime: Option<SystemTime>,
pub disk_len: Option<u64>,
pub disk_state: DiskState,
pub swap_path: Option<std::path::PathBuf>,
pub last_swap_dirty_gen: Option<u64>,
pub(super) last_fold_dirty_gen: Option<u64>,
pub(crate) blame: Vec<Option<hjkl_app::git::BlameInfo>>,
pub(crate) last_blame_dirty_gen: Option<u64>,
pub(crate) last_blame_refresh_at: Instant,
}
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) = self.cached_signature();
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 current_len = self.editor.buffer().byte_len();
if current_len != self.saved_len {
self.dirty = true;
return t.elapsed().as_micros();
}
let (h, _) = self.cached_signature();
self.dirty = h != self.saved_hash;
t.elapsed().as_micros()
}
fn cached_signature(&mut self) -> (u64, usize) {
let dg = self.editor.buffer().dirty_gen();
if let Some((cached_dg, sig)) = self.signature_cache
&& cached_dg == dg
{
return sig;
}
let sig = buffer_signature(&self.editor);
self.signature_cache = Some((dg, sig));
sig
}
}