use std::collections::HashMap;
use liwe::graph::Graph;
use liwe::locale::get_locale;
use liwe::model::config::{
ActionDefinition, Command, Configuration, MarkdownOptions, DEFAULT_KEY_DATE_FORMAT,
};
use liwe::model::tree::Tree;
use liwe::model::{Key, Markdown, NodeId};
use lsp_types::{CodeAction, CodeActionKind, CodeActionOrCommand};
use once_cell::sync::Lazy;
use std::sync::Mutex;
use super::{BasePath, ChangeExt};
mod attach;
mod delete;
mod extract;
mod extract_all;
mod inline;
mod link;
mod list;
mod section;
mod sort;
pub mod templates;
mod transform;
pub use attach::AttachAction;
pub use delete::DeleteAction;
pub use extract::SectionExtract;
pub use extract_all::ExtractAll;
pub use inline::InlineAction;
pub use link::LinkAction;
pub use list::{ListChangeType, ListToSections};
pub use section::SectionToList;
pub use sort::SortAction;
pub use transform::TransformBlockAction;
pub trait ActionContext {
fn key_of(&self, node_id: NodeId) -> Key;
fn key_exists(&self, key: &Key) -> bool;
fn collect(&self, key: &Key) -> Tree;
fn squash(&self, key: &Key, depth: u8) -> Tree;
fn random_key(&self, parent: &str) -> Key;
fn random_keys(&self, parent: &str, number: usize) -> Vec<Key>;
fn unique_ids(&self, parent: &str, number: usize) -> Vec<String>;
fn markdown_options(&self) -> &MarkdownOptions;
fn get_command(&self, name: &str) -> Option<&Command>;
fn graph(&self) -> &Graph;
fn patch(&self) -> Graph;
fn get_inclusion_edges_to(&self, key: &Key) -> Vec<NodeId>;
fn get_reference_edges_to(&self, key: &Key) -> Vec<NodeId>;
fn get_ref_text(&self, key: &Key) -> Option<String>;
fn get_node_id_at(&self, key: &Key, line: usize) -> Option<NodeId>;
fn get_document_markdown(&self, key: &Key) -> Option<String>;
fn get_link_key_at(&self, key: &Key, line: usize, character: usize) -> Option<Key>;
fn get_link_text_at(&self, key: &Key, line: usize, character: usize) -> Option<String>;
fn now(&self) -> std::time::SystemTime;
}
#[derive(Clone)]
pub struct Action {
pub title: String,
pub identifier: String,
pub key: Key,
pub range: TextRange,
}
#[derive(Clone)]
pub struct Position {
pub line: u32,
pub character: u32,
}
#[derive(Clone)]
pub struct TextRange {
pub start: Position,
pub end: Position,
}
pub enum Change {
Remove(Remove),
Create(Create),
Update(Update),
}
pub type Changes = Vec<Change>;
pub struct Create {
pub key: Key,
}
pub struct Update {
pub key: Key,
pub markdown: Markdown,
}
pub struct Remove {
pub key: Key,
}
impl Action {
pub fn to_code_action(&self) -> CodeActionOrCommand {
CodeActionOrCommand::CodeAction(CodeAction {
title: self.title.to_string(),
kind: Some(identifier_to_action_kind(self.identifier.to_string())),
data: Some(serde_json::json!({
"key": self.key.to_string(),
"range": {
"start": {
"line": self.range.start.line,
"character": self.range.start.character,
},
"end": {
"line": self.range.end.line,
"character": self.range.end.character,
}
}
})),
..Default::default()
})
}
pub fn resolve_code_action(
&self,
base_path: &BasePath,
changes: Changes,
) -> CodeActionOrCommand {
use itertools::Itertools;
use lsp_types::{DocumentChanges, WorkspaceEdit};
CodeActionOrCommand::CodeAction(CodeAction {
title: self.title.to_string(),
kind: Some(identifier_to_action_kind(self.identifier.to_string())),
edit: Some(WorkspaceEdit {
document_changes: Some(DocumentChanges::Operations(
changes
.iter()
.map(|change| change.to_document_change(base_path))
.collect_vec(),
)),
..Default::default()
}),
..Default::default()
})
}
}
pub trait ActionProvider {
fn identifier(&self) -> String;
fn action(&self, key: Key, selection: TextRange, context: impl ActionContext)
-> Option<Action>;
fn changes(
&self,
key: Key,
selection: TextRange,
context: impl ActionContext,
) -> Option<liwe::operations::Changes>;
fn action_kind(&self) -> CodeActionKind {
identifier_to_action_kind(self.identifier())
}
}
pub enum ActionEnum {
ListChangeType(ListChangeType),
ListToSections(ListToSections),
SectionToList(SectionToList),
SectionExtract(SectionExtract),
ExtractAll(ExtractAll),
TransformBlockAction(TransformBlockAction),
AttachAction(AttachAction),
SortAction(SortAction),
InlineAction(InlineAction),
DeleteAction(DeleteAction),
LinkAction(LinkAction),
}
impl ActionProvider for ActionEnum {
fn identifier(&self) -> String {
match self {
ActionEnum::ListChangeType(inner) => inner.identifier(),
ActionEnum::ListToSections(inner) => inner.identifier(),
ActionEnum::SectionToList(inner) => inner.identifier(),
ActionEnum::SectionExtract(inner) => inner.identifier(),
ActionEnum::ExtractAll(inner) => inner.identifier(),
ActionEnum::TransformBlockAction(inner) => inner.identifier(),
ActionEnum::AttachAction(inner) => inner.identifier(),
ActionEnum::SortAction(inner) => inner.identifier(),
ActionEnum::InlineAction(inner) => inner.identifier(),
ActionEnum::DeleteAction(inner) => inner.identifier(),
ActionEnum::LinkAction(inner) => inner.identifier(),
}
}
fn action(
&self,
key: Key,
selection: TextRange,
context: impl ActionContext,
) -> Option<Action> {
match self {
ActionEnum::ListChangeType(inner) => inner.action(key, selection, context),
ActionEnum::ListToSections(inner) => inner.action(key, selection, context),
ActionEnum::SectionToList(inner) => inner.action(key, selection, context),
ActionEnum::SectionExtract(inner) => inner.action(key, selection, context),
ActionEnum::ExtractAll(inner) => inner.action(key, selection, context),
ActionEnum::TransformBlockAction(inner) => inner.action(key, selection, context),
ActionEnum::AttachAction(inner) => inner.action(key, selection, context),
ActionEnum::SortAction(inner) => inner.action(key, selection, context),
ActionEnum::InlineAction(inner) => inner.action(key, selection, context),
ActionEnum::DeleteAction(inner) => inner.action(key, selection, context),
ActionEnum::LinkAction(inner) => inner.action(key, selection, context),
}
}
fn changes(
&self,
key: Key,
selection: TextRange,
context: impl ActionContext,
) -> Option<liwe::operations::Changes> {
match self {
ActionEnum::ListChangeType(inner) => inner.changes(key, selection, context),
ActionEnum::ListToSections(inner) => inner.changes(key, selection, context),
ActionEnum::SectionToList(inner) => inner.changes(key, selection, context),
ActionEnum::SectionExtract(inner) => inner.changes(key, selection, context),
ActionEnum::ExtractAll(inner) => inner.changes(key, selection, context),
ActionEnum::TransformBlockAction(inner) => inner.changes(key, selection, context),
ActionEnum::AttachAction(inner) => inner.changes(key, selection, context),
ActionEnum::SortAction(inner) => inner.changes(key, selection, context),
ActionEnum::InlineAction(inner) => inner.changes(key, selection, context),
ActionEnum::DeleteAction(inner) => inner.changes(key, selection, context),
ActionEnum::LinkAction(inner) => inner.changes(key, selection, context),
}
}
}
static CODE_ACTION_MAP: Lazy<Mutex<HashMap<String, CodeActionKind>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub fn identifier_to_action_kind(identifier: String) -> CodeActionKind {
let mut map = CODE_ACTION_MAP
.lock()
.expect("CODE_ACTION_MAP mutex poisoned");
map.entry(identifier.clone())
.or_insert_with(|| CodeActionKind::new(identifier.clone().leak()))
.clone()
}
pub fn all_actions() -> Vec<ActionEnum> {
vec![
ActionEnum::ListChangeType(ListChangeType {}),
ActionEnum::ListToSections(ListToSections {}),
ActionEnum::SectionToList(SectionToList {}),
]
}
pub fn all_action_types(configuration: &Configuration) -> Vec<ActionEnum> {
let mut actions = vec![
ActionEnum::ListChangeType(ListChangeType {}),
ActionEnum::ListToSections(ListToSections {}),
ActionEnum::SectionToList(SectionToList {}),
ActionEnum::DeleteAction(DeleteAction {}),
];
let key_locale = get_locale(configuration.library.locale.as_deref());
let markdown_locale = get_locale(configuration.markdown.locale.as_deref());
actions.extend(
configuration
.actions
.iter()
.map(|(identifier, action)| match action {
ActionDefinition::Transform(transform) => {
ActionEnum::TransformBlockAction(TransformBlockAction {
title: transform.title.clone(),
identifier: identifier.clone(),
command: transform.command.clone(),
input_template: transform.input_template.clone(),
})
}
ActionDefinition::Attach(attach) => {
let md_date_fmt = configuration
.clone()
.markdown
.date_format
.unwrap_or("%b %d, %Y".into());
let key_date_fmt = configuration
.clone()
.library
.date_format
.unwrap_or(DEFAULT_KEY_DATE_FORMAT.into());
ActionEnum::AttachAction(AttachAction {
title: attach.title.clone(),
identifier: identifier.clone(),
document_template: attach.document_template.clone(),
key_template: attach.key_template.clone(),
markdown_date_format: md_date_fmt.clone(),
markdown_time_format: configuration
.clone()
.markdown
.time_format
.unwrap_or_else(|| md_date_fmt.clone()),
key_date_format: key_date_fmt.clone(),
key_time_format: configuration
.clone()
.library
.time_format
.unwrap_or_else(|| key_date_fmt.clone()),
key_locale,
markdown_locale,
})
}
ActionDefinition::Sort(sort) => {
ActionEnum::SortAction(SortAction {
title: sort.title.clone(),
identifier: identifier.clone(),
reverse: sort.reverse.unwrap_or(false),
})
}
ActionDefinition::Inline(inline) => {
ActionEnum::InlineAction(InlineAction {
title: inline.title.clone(),
identifier: identifier.clone(),
inline_type: inline.inline_type.clone(),
keep_target: inline.keep_target.unwrap_or(false),
})
}
ActionDefinition::Extract(extract) => {
ActionEnum::SectionExtract(SectionExtract {
title: extract.title.clone(),
identifier: identifier.clone(),
link_type: extract.link_type.clone(),
key_template: extract.key_template.clone(),
key_date_format: configuration
.clone()
.library
.date_format
.unwrap_or(DEFAULT_KEY_DATE_FORMAT.into()),
locale: key_locale,
})
}
ActionDefinition::ExtractAll(extract_all) => {
ActionEnum::ExtractAll(ExtractAll {
title: extract_all.title.clone(),
identifier: identifier.clone(),
link_type: extract_all.link_type.clone(),
key_template: extract_all.key_template.clone(),
key_date_format: configuration
.clone()
.library
.date_format
.unwrap_or(DEFAULT_KEY_DATE_FORMAT.into()),
locale: key_locale,
})
}
ActionDefinition::Link(link) => {
ActionEnum::LinkAction(LinkAction {
title: link.title.clone(),
identifier: identifier.clone(),
link_type: link.link_type.clone(),
key_template: link.key_template.clone(),
key_date_format: configuration
.clone()
.library
.date_format
.unwrap_or(DEFAULT_KEY_DATE_FORMAT.into()),
locale: key_locale,
})
}
}),
);
actions
}
pub use liwe::operations::string_to_slug;
pub fn into_lsp_changes(liwe_changes: liwe::operations::Changes) -> Changes {
let mut changes = Vec::new();
for (key, markdown) in liwe_changes.creates {
changes.push(Change::Create(Create { key: key.clone() }));
changes.push(Change::Update(Update { key, markdown }));
}
for key in liwe_changes.removes {
changes.push(Change::Remove(Remove { key }));
}
for (key, markdown) in liwe_changes.updates {
changes.push(Change::Update(Update { key, markdown }));
}
changes
}