use std::num::NonZeroUsize;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use anyhow::{Result, anyhow};
use fieldwork::Fieldwork;
use lru::LruCache;
use serde::{Deserialize, Serialize};
use crate::editor::EditPosition;
use crate::languages::{LanguageName, LanguageRegistry};
use crate::selector::Selector;
use mcplease::session::SessionStore;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SemanticEditSessionData {
pub context_path: Option<PathBuf>,
pub staged_operation: Option<StagedOperation>,
}
#[derive(Debug, Clone, Fieldwork, Serialize, Deserialize)]
#[fieldwork(get, set, get_mut, with)]
pub struct StagedOperation {
pub selector: Selector,
pub content: String,
pub file_path: PathBuf,
pub language_name: LanguageName,
pub edit_position: Option<EditPosition>,
}
impl StagedOperation {
pub fn retarget(&mut self, selector: Selector) {
self.selector = selector;
}
}
#[derive(fieldwork::Fieldwork)]
#[fieldwork(get)]
pub struct SemanticEditTools {
#[fieldwork(get_mut)]
session_store: SessionStore<SemanticEditSessionData>,
language_registry: Arc<LanguageRegistry>,
file_cache: Arc<Mutex<LruCache<String, String>>>,
#[fieldwork(set, get_mut, option = false)]
commit_fn: Option<Box<(dyn Fn(PathBuf, String) + 'static)>>,
#[fieldwork(set, with)]
default_session_id: &'static str,
}
impl std::fmt::Debug for SemanticEditTools {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SemanticEditTools")
.field("session_store", &self.session_store)
.field("language_registry", &self.language_registry)
.field("file_cache", &self.file_cache)
.field("default_session_id", &self.default_session_id)
.finish()
}
}
impl SemanticEditTools {
pub fn new(storage_path: Option<&str>) -> Result<Self> {
let storage_path = storage_path.map(|s| PathBuf::from(&*shellexpand::tilde(s)));
let session_store = SessionStore::new(storage_path)?;
let language_registry = Arc::new(LanguageRegistry::new()?);
let file_cache = Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(50).unwrap())));
Ok(Self {
session_store,
language_registry,
file_cache,
commit_fn: None,
default_session_id: "default",
})
}
pub fn get_context(&self, session_id: Option<&str>) -> Result<Option<PathBuf>> {
let session_id = session_id.unwrap_or_else(|| self.default_session_id());
let session_data = self.session_store.get_or_create(session_id)?;
Ok(session_data.context_path)
}
pub fn stage_operation(
&self,
session_id: Option<&str>,
staged_operation: Option<StagedOperation>,
) -> Result<()> {
let session_id = session_id.unwrap_or_else(|| self.default_session_id());
self.session_store.update(session_id, |data| {
data.staged_operation = staged_operation;
})
}
pub fn get_staged_operation(
&self,
session_id: Option<&str>,
) -> Result<Option<StagedOperation>> {
let session_id = session_id.unwrap_or_else(|| self.default_session_id());
let session_data = self.session_store.get_or_create(session_id)?;
Ok(session_data.staged_operation)
}
pub fn take_staged_operation(
&self,
session_id: Option<&str>,
) -> Result<Option<StagedOperation>> {
let mut staged_op = None;
let session_id = session_id.unwrap_or_else(|| self.default_session_id());
self.session_store.update(session_id, |data| {
staged_op = data.staged_operation.take();
})?;
Ok(staged_op)
}
pub fn modify_staged_operation<F>(
&self,
session_id: Option<&str>,
fun: F,
) -> Result<Option<StagedOperation>>
where
F: FnOnce(&mut StagedOperation),
{
let session_id = session_id.unwrap_or_else(|| self.default_session_id());
self.session_store.update(session_id, |data| {
if let Some(ref mut op) = data.staged_operation {
fun(op);
}
})?;
self.get_staged_operation(Some(session_id))
}
pub fn set_context(&self, session_id: Option<&str>, path: PathBuf) -> Result<()> {
let session_id = session_id.unwrap_or_else(|| self.default_session_id());
self.session_store.update(session_id, |data| {
data.context_path = Some(path);
})
}
pub(crate) fn resolve_path(&self, path_str: &str, session_id: Option<&str>) -> Result<PathBuf> {
let path = PathBuf::from(&*shellexpand::tilde(path_str));
if path.is_absolute() {
return Ok(std::fs::canonicalize(path)?);
}
let session_id = session_id.unwrap_or_else(|| self.default_session_id());
match self.get_context(Some(session_id))? {
Some(context) => Ok(std::fs::canonicalize(context.join(path_str))?),
None => Err(anyhow!(
"No context found for `{session_id}`. Use set_context first or provide an absolute path.",
)),
}
}
}