ok/jj
1
0
Fork 0
forked from mirrors/jj

templater: pass diagnostics receiver around

This commit is contained in:
Yuya Nishihara 2024-09-19 17:55:38 +09:00
parent 8b1760ca5d
commit 11286b3072
8 changed files with 752 additions and 431 deletions

View file

@ -122,7 +122,7 @@ impl CommitTemplateLanguageExtension for HexCounter {
let mut table = CommitTemplateBuildFnTable::empty();
table.commit_methods.insert(
"has_most_digits",
|language, _build_context, property, call| {
|language, _diagnostics, _build_context, property, call| {
call.expect_no_arguments()?;
let most_digits = language
.cache_extension::<MostDigitsInId>()
@ -135,7 +135,7 @@ impl CommitTemplateLanguageExtension for HexCounter {
);
table.commit_methods.insert(
"num_digits_in_id",
|_language, _build_context, property, call| {
|_language, _diagnostics, _build_context, property, call| {
call.expect_no_arguments()?;
Ok(L::wrap_integer(
property.map(|commit| num_digits_in_id(commit.id())),
@ -144,7 +144,7 @@ impl CommitTemplateLanguageExtension for HexCounter {
);
table.commit_methods.insert(
"num_char_in_id",
|_language, _build_context, property, call| {
|_language, _diagnostics, _build_context, property, call| {
let [string_arg] = call.expect_exact_arguments()?;
let char_arg =
template_parser::expect_string_literal_with(string_arg, |string, span| {

View file

@ -53,7 +53,7 @@ impl OperationTemplateLanguageExtension for HexCounter {
let mut table = OperationTemplateBuildFnTable::empty();
table.operation_methods.insert(
"num_digits_in_id",
|_language, _build_context, property, call| {
|_language, _diagnostics, _build_context, property, call| {
call.expect_no_arguments()?;
Ok(L::wrap_integer(
property.map(|operation| num_digits_in_id(operation.id())),
@ -62,7 +62,7 @@ impl OperationTemplateLanguageExtension for HexCounter {
);
table.operation_methods.insert(
"num_char_in_id",
|_language, _build_context, property, call| {
|_language, _diagnostics, _build_context, property, call| {
let [string_arg] = call.expect_exact_arguments()?;
let char_arg =
template_parser::expect_string_literal_with(string_arg, |string, span| {

View file

@ -166,7 +166,7 @@ use crate::revset_util::RevsetExpressionEvaluator;
use crate::template_builder;
use crate::template_builder::TemplateLanguage;
use crate::template_parser::TemplateAliasesMap;
use crate::template_parser::TemplateParseResult;
use crate::template_parser::TemplateDiagnostics;
use crate::templater::PropertyPlaceholder;
use crate::templater::TemplateRenderer;
use crate::text_util;
@ -343,13 +343,17 @@ impl CommandHelper {
template_text: &str,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
) -> Result<TemplateRenderer<'a, C>, CommandError> {
let mut diagnostics = TemplateDiagnostics::new();
let aliases = self.load_template_aliases(ui)?;
Ok(template_builder::parse(
let template = template_builder::parse(
language,
&mut diagnostics,
template_text,
&aliases,
wrap_self,
)?)
)?;
print_parse_diagnostics(ui, "In template expression", &diagnostics)?;
Ok(template)
}
pub fn workspace_loader(&self) -> Result<&dyn WorkspaceLoader, CommandError> {
@ -780,13 +784,21 @@ impl WorkspaceCommandEnvironment {
/// be one of the `L::wrap_*()` functions.
pub fn parse_template<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>(
&self,
_ui: &Ui,
ui: &Ui,
language: &L,
template_text: &str,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
) -> TemplateParseResult<TemplateRenderer<'a, C>> {
let aliases = &self.template_aliases_map;
template_builder::parse(language, template_text, aliases, wrap_self)
) -> Result<TemplateRenderer<'a, C>, CommandError> {
let mut diagnostics = TemplateDiagnostics::new();
let template = template_builder::parse(
language,
&mut diagnostics,
template_text,
&self.template_aliases_map,
wrap_self,
)?;
print_parse_diagnostics(ui, "In template expression", &diagnostics)?;
Ok(template)
}
/// Creates commit template language environment for this workspace and the
@ -1392,7 +1404,7 @@ impl WorkspaceCommandHelper {
language: &L,
template_text: &str,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
) -> TemplateParseResult<TemplateRenderer<'a, C>> {
) -> Result<TemplateRenderer<'a, C>, CommandError> {
self.env
.parse_template(ui, language, template_text, wrap_self)
}
@ -1404,9 +1416,14 @@ impl WorkspaceCommandHelper {
template_text: &str,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
) -> TemplateRenderer<'a, C> {
let aliases = &self.env.template_aliases_map;
template_builder::parse(language, template_text, aliases, wrap_self)
.expect("parse error should be confined by WorkspaceCommandHelper::new()")
template_builder::parse(
language,
&mut TemplateDiagnostics::new(),
template_text,
&self.env.template_aliases_map,
wrap_self,
)
.expect("parse error should be confined by WorkspaceCommandHelper::new()")
}
/// Parses commit template into evaluation tree.
@ -1414,7 +1431,7 @@ impl WorkspaceCommandHelper {
&self,
ui: &Ui,
template_text: &str,
) -> TemplateParseResult<TemplateRenderer<'_, Commit>> {
) -> Result<TemplateRenderer<'_, Commit>, CommandError> {
let language = self.commit_template_language();
self.parse_template(
ui,
@ -1429,7 +1446,7 @@ impl WorkspaceCommandHelper {
&self,
ui: &Ui,
template_text: &str,
) -> TemplateParseResult<TemplateRenderer<'_, Operation>> {
) -> Result<TemplateRenderer<'_, Operation>, CommandError> {
let language = self.operation_template_language();
self.parse_template(
ui,
@ -2056,7 +2073,7 @@ impl WorkspaceCommandTransaction<'_> {
&self,
ui: &Ui,
template_text: &str,
) -> TemplateParseResult<TemplateRenderer<'_, Commit>> {
) -> Result<TemplateRenderer<'_, Commit>, CommandError> {
let language = self.commit_template_language();
self.helper.env.parse_template(
ui,

View file

@ -64,6 +64,7 @@ use crate::template_builder::TemplateLanguage;
use crate::template_parser;
use crate::template_parser::ExpressionNode;
use crate::template_parser::FunctionCallNode;
use crate::template_parser::TemplateDiagnostics;
use crate::template_parser::TemplateParseError;
use crate::template_parser::TemplateParseResult;
use crate::templater;
@ -142,15 +143,17 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
fn build_function(
&self,
diagnostics: &mut TemplateDiagnostics,
build_ctx: &BuildContext<Self::Property>,
function: &FunctionCallNode,
) -> TemplateParseResult<Self::Property> {
let table = &self.build_fn_table.core;
table.build_function(self, build_ctx, function)
table.build_function(self, diagnostics, build_ctx, function)
}
fn build_method(
&self,
diagnostics: &mut TemplateDiagnostics,
build_ctx: &BuildContext<Self::Property>,
property: Self::Property,
function: &FunctionCallNode,
@ -159,24 +162,31 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
match property {
CommitTemplatePropertyKind::Core(property) => {
let table = &self.build_fn_table.core;
table.build_method(self, build_ctx, property, function)
table.build_method(self, diagnostics, build_ctx, property, function)
}
CommitTemplatePropertyKind::Commit(property) => {
let table = &self.build_fn_table.commit_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
build(self, build_ctx, property, function)
build(self, diagnostics, build_ctx, property, function)
}
CommitTemplatePropertyKind::CommitOpt(property) => {
let type_name = "Commit";
let table = &self.build_fn_table.commit_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
let inner_property = property.try_unwrap(type_name);
build(self, build_ctx, Box::new(inner_property), function)
build(
self,
diagnostics,
build_ctx,
Box::new(inner_property),
function,
)
}
CommitTemplatePropertyKind::CommitList(property) => {
// TODO: migrate to table?
template_builder::build_unformattable_list_method(
self,
diagnostics,
build_ctx,
property,
function,
@ -186,19 +196,26 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
CommitTemplatePropertyKind::RefName(property) => {
let table = &self.build_fn_table.ref_name_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
build(self, build_ctx, property, function)
build(self, diagnostics, build_ctx, property, function)
}
CommitTemplatePropertyKind::RefNameOpt(property) => {
let type_name = "RefName";
let table = &self.build_fn_table.ref_name_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
let inner_property = property.try_unwrap(type_name);
build(self, build_ctx, Box::new(inner_property), function)
build(
self,
diagnostics,
build_ctx,
Box::new(inner_property),
function,
)
}
CommitTemplatePropertyKind::RefNameList(property) => {
// TODO: migrate to table?
template_builder::build_formattable_list_method(
self,
diagnostics,
build_ctx,
property,
function,
@ -208,17 +225,17 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
CommitTemplatePropertyKind::CommitOrChangeId(property) => {
let table = &self.build_fn_table.commit_or_change_id_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
build(self, build_ctx, property, function)
build(self, diagnostics, build_ctx, property, function)
}
CommitTemplatePropertyKind::ShortestIdPrefix(property) => {
let table = &self.build_fn_table.shortest_id_prefix_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
build(self, build_ctx, property, function)
build(self, diagnostics, build_ctx, property, function)
}
CommitTemplatePropertyKind::TreeDiff(property) => {
let table = &self.build_fn_table.tree_diff_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
build(self, build_ctx, property, function)
build(self, diagnostics, build_ctx, property, function)
}
}
}
@ -500,7 +517,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
let mut map = CommitTemplateBuildMethodFnMap::<Commit>::new();
map.insert(
"description",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property =
self_property.map(|commit| text_util::complete_newline(commit.description()));
@ -509,7 +526,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"change_id",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property =
self_property.map(|commit| CommitOrChangeId::Change(commit.change_id().to_owned()));
@ -518,7 +535,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"commit_id",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property =
self_property.map(|commit| CommitOrChangeId::Commit(commit.id().to_owned()));
@ -527,7 +544,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"parents",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property =
self_property.and_then(|commit| Ok(commit.parents().try_collect()?));
@ -536,7 +553,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"author",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|commit| commit.author().clone());
Ok(L::wrap_signature(out_property))
@ -544,21 +561,24 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"committer",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|commit| commit.committer().clone());
Ok(L::wrap_signature(out_property))
},
);
map.insert("mine", |language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let user_email = language.revset_parse_context.user_email().to_owned();
let out_property = self_property.map(move |commit| commit.author().email == user_email);
Ok(L::wrap_boolean(out_property))
});
map.insert(
"mine",
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let user_email = language.revset_parse_context.user_email().to_owned();
let out_property = self_property.map(move |commit| commit.author().email == user_email);
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"working_copies",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.map(|commit| extract_working_copies(repo, &commit));
@ -567,7 +587,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"current_working_copy",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let workspace_id = language.workspace_id.clone();
@ -579,7 +599,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"bookmarks",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let index = language
.keyword_cache
@ -598,7 +618,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"local_bookmarks",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let index = language
.keyword_cache
@ -617,7 +637,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"remote_bookmarks",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let index = language
.keyword_cache
@ -639,15 +659,18 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
map.insert("local_branches", map["local_bookmarks"]);
map.insert("remote_branches", map["remote_bookmarks"]);
map.insert("tags", |language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let index = language.keyword_cache.tags_index(language.repo).clone();
let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec());
Ok(L::wrap_ref_name_list(out_property))
});
map.insert(
"tags",
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let index = language.keyword_cache.tags_index(language.repo).clone();
let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec());
Ok(L::wrap_ref_name_list(out_property))
},
);
map.insert(
"git_refs",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let index = language.keyword_cache.git_refs_index(language.repo).clone();
let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec());
@ -656,7 +679,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"git_head",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.map(|commit| extract_git_head(repo, &commit));
@ -665,7 +688,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"divergent",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.map(|commit| {
@ -676,18 +699,21 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
Ok(L::wrap_boolean(out_property))
},
);
map.insert("hidden", |language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.map(|commit| {
let maybe_entries = repo.resolve_change_id(commit.change_id());
maybe_entries.map_or(true, |entries| !entries.contains(commit.id()))
});
Ok(L::wrap_boolean(out_property))
});
map.insert(
"hidden",
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.map(|commit| {
let maybe_entries = repo.resolve_change_id(commit.change_id());
maybe_entries.map_or(true, |entries| !entries.contains(commit.id()))
});
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"immutable",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let is_immutable = language
.keyword_cache
@ -699,12 +725,12 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"contained_in",
|language, _build_ctx, self_property, function| {
|language, diagnostics, _build_ctx, self_property, function| {
let [revset_node] = function.expect_exact_arguments()?;
let is_contained =
template_parser::expect_string_literal_with(revset_node, |revset, span| {
Ok(evaluate_user_revset(language, span, revset)?.containing_fn())
Ok(evaluate_user_revset(language, diagnostics, span, revset)?.containing_fn())
})?;
let out_property = self_property.map(move |commit| is_contained(commit.id()));
@ -713,39 +739,49 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
);
map.insert(
"conflict",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.and_then(|commit| Ok(commit.has_conflict()?));
Ok(L::wrap_boolean(out_property))
},
);
map.insert("empty", |language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.and_then(|commit| Ok(commit.is_empty(repo)?));
Ok(L::wrap_boolean(out_property))
});
map.insert("diff", |language, _build_ctx, self_property, function| {
let ([], [files_node]) = function.expect_arguments()?;
let files = if let Some(node) = files_node {
expect_fileset_literal(node, language.path_converter)?
} else {
// TODO: defaults to CLI path arguments?
// https://github.com/martinvonz/jj/issues/2933#issuecomment-1925870731
FilesetExpression::all()
};
let repo = language.repo;
let matcher: Rc<dyn Matcher> = files.to_matcher().into();
let out_property = self_property
.and_then(move |commit| Ok(TreeDiff::from_commit(repo, &commit, matcher.clone())?));
Ok(L::wrap_tree_diff(out_property))
});
map.insert("root", |language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.map(|commit| commit.id() == repo.store().root_commit_id());
Ok(L::wrap_boolean(out_property))
});
map.insert(
"empty",
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.and_then(|commit| Ok(commit.is_empty(repo)?));
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"diff",
|language, diagnostics, _build_ctx, self_property, function| {
let ([], [files_node]) = function.expect_arguments()?;
let files = if let Some(node) = files_node {
expect_fileset_literal(diagnostics, node, language.path_converter)?
} else {
// TODO: defaults to CLI path arguments?
// https://github.com/martinvonz/jj/issues/2933#issuecomment-1925870731
FilesetExpression::all()
};
let repo = language.repo;
let matcher: Rc<dyn Matcher> = files.to_matcher().into();
let out_property = self_property
.and_then(move |commit| Ok(TreeDiff::from_commit(repo, &commit, matcher.clone())?));
Ok(L::wrap_tree_diff(out_property))
},
);
map.insert(
"root",
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property =
self_property.map(|commit| commit.id() == repo.store().root_commit_id());
Ok(L::wrap_boolean(out_property))
},
);
map
}
@ -765,14 +801,20 @@ fn extract_working_copies(repo: &dyn Repo, commit: &Commit) -> String {
}
fn expect_fileset_literal(
diagnostics: &mut TemplateDiagnostics,
node: &ExpressionNode,
path_converter: &RepoPathUiConverter,
) -> Result<FilesetExpression, TemplateParseError> {
template_parser::expect_string_literal_with(node, |text, span| {
let mut inner_diagnostics = FilesetDiagnostics::new(); // TODO
fileset::parse(&mut inner_diagnostics, text, path_converter).map_err(|err| {
TemplateParseError::expression("In fileset expression", span).with_source(err)
})
let mut inner_diagnostics = FilesetDiagnostics::new();
let expression =
fileset::parse(&mut inner_diagnostics, text, path_converter).map_err(|err| {
TemplateParseError::expression("In fileset expression", span).with_source(err)
})?;
diagnostics.extend_with(inner_diagnostics, |diag| {
TemplateParseError::expression("In fileset expression", span).with_source(diag)
});
Ok(expression)
})
}
@ -797,16 +839,20 @@ fn evaluate_revset_expression<'repo>(
fn evaluate_user_revset<'repo>(
language: &CommitTemplateLanguage<'repo>,
diagnostics: &mut TemplateDiagnostics,
span: pest::Span<'_>,
revset: &str,
) -> Result<Box<dyn Revset + 'repo>, TemplateParseError> {
let mut inner_diagnostics = RevsetDiagnostics::new(); // TODO
let mut inner_diagnostics = RevsetDiagnostics::new();
let (expression, modifier) = revset::parse_with_modifier(
&mut inner_diagnostics,
revset,
&language.revset_parse_context,
)
.map_err(|err| TemplateParseError::expression("In revset expression", span).with_source(err))?;
diagnostics.extend_with(inner_diagnostics, |diag| {
TemplateParseError::expression("In revset expression", span).with_source(diag)
});
let (None | Some(RevsetModifier::All)) = modifier;
evaluate_revset_expression(language, span, expression)
@ -1003,14 +1049,17 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
// Not using maplit::hashmap!{} or custom declarative macro here because
// code completion inside macro is quite restricted.
let mut map = CommitTemplateBuildMethodFnMap::<Rc<RefName>>::new();
map.insert("name", |_language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|ref_name| ref_name.name.clone());
Ok(L::wrap_string(out_property))
});
map.insert(
"name",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|ref_name| ref_name.name.clone());
Ok(L::wrap_string(out_property))
},
);
map.insert(
"remote",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property =
self_property.map(|ref_name| ref_name.remote.clone().unwrap_or_default());
@ -1019,7 +1068,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"present",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|ref_name| ref_name.is_present());
Ok(L::wrap_boolean(out_property))
@ -1027,7 +1076,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"conflict",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|ref_name| ref_name.has_conflict());
Ok(L::wrap_boolean(out_property))
@ -1035,7 +1084,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"normal_target",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.and_then(|ref_name| {
@ -1047,7 +1096,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"removed_targets",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.and_then(|ref_name| {
@ -1059,7 +1108,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"added_targets",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property = self_property.and_then(|ref_name| {
@ -1071,7 +1120,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"tracked",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|ref_name| ref_name.is_tracked());
Ok(L::wrap_boolean(out_property))
@ -1079,7 +1128,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"tracking_present",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|ref_name| ref_name.is_tracking_present());
Ok(L::wrap_boolean(out_property))
@ -1087,7 +1136,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"tracking_ahead_count",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property =
@ -1097,7 +1146,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
);
map.insert(
"tracking_behind_count",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let repo = language.repo;
let out_property =
@ -1225,7 +1274,7 @@ fn builtin_commit_or_change_id_methods<'repo>(
let mut map = CommitTemplateBuildMethodFnMap::<CommitOrChangeId>::new();
map.insert(
"normal_hex",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
Ok(L::wrap_string(self_property.map(|id| {
// Note: this is _not_ the same as id.hex() for ChangeId, which
@ -1238,22 +1287,39 @@ fn builtin_commit_or_change_id_methods<'repo>(
})))
},
);
map.insert("short", |language, build_ctx, self_property, function| {
let ([], [len_node]) = function.expect_arguments()?;
let len_property = len_node
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
.transpose()?;
let out_property =
(self_property, len_property).map(|(id, len)| id.short(len.unwrap_or(12)));
Ok(L::wrap_string(out_property))
});
map.insert(
"short",
|language, diagnostics, build_ctx, self_property, function| {
let ([], [len_node]) = function.expect_arguments()?;
let len_property = len_node
.map(|node| {
template_builder::expect_usize_expression(
language,
diagnostics,
build_ctx,
node,
)
})
.transpose()?;
let out_property =
(self_property, len_property).map(|(id, len)| id.short(len.unwrap_or(12)));
Ok(L::wrap_string(out_property))
},
);
map.insert(
"shortest",
|language, build_ctx, self_property, function| {
|language, diagnostics, build_ctx, self_property, function| {
let id_prefix_context = &language.id_prefix_context;
let ([], [len_node]) = function.expect_arguments()?;
let len_property = len_node
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
.map(|node| {
template_builder::expect_usize_expression(
language,
diagnostics,
build_ctx,
node,
)
})
.transpose()?;
let out_property = (self_property, len_property)
.map(|(id, len)| id.shortest(language.repo, id_prefix_context, len.unwrap_or(0)));
@ -1299,27 +1365,36 @@ fn builtin_shortest_id_prefix_methods<'repo>(
let mut map = CommitTemplateBuildMethodFnMap::<ShortestIdPrefix>::new();
map.insert(
"prefix",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.prefix);
Ok(L::wrap_string(out_property))
},
);
map.insert("rest", |_language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.rest);
Ok(L::wrap_string(out_property))
});
map.insert("upper", |_language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.to_upper());
Ok(L::wrap_shortest_id_prefix(out_property))
});
map.insert("lower", |_language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.to_lower());
Ok(L::wrap_shortest_id_prefix(out_property))
});
map.insert(
"rest",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.rest);
Ok(L::wrap_string(out_property))
},
);
map.insert(
"upper",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.to_upper());
Ok(L::wrap_shortest_id_prefix(out_property))
},
);
map.insert(
"lower",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.to_lower());
Ok(L::wrap_shortest_id_prefix(out_property))
},
);
map
}
@ -1392,10 +1467,17 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
let mut map = CommitTemplateBuildMethodFnMap::<TreeDiff>::new();
map.insert(
"color_words",
|language, build_ctx, self_property, function| {
|language, diagnostics, build_ctx, self_property, function| {
let ([], [context_node]) = function.expect_arguments()?;
let context_property = context_node
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
.map(|node| {
template_builder::expect_usize_expression(
language,
diagnostics,
build_ctx,
node,
)
})
.transpose()?;
let path_converter = language.path_converter;
let template = (self_property, context_property)
@ -1419,38 +1501,61 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
Ok(L::wrap_template(template))
},
);
map.insert("git", |language, build_ctx, self_property, function| {
let ([], [context_node]) = function.expect_arguments()?;
let context_property = context_node
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
.transpose()?;
let template = (self_property, context_property)
.map(|(diff, context)| {
let context = context.unwrap_or(diff_util::DEFAULT_CONTEXT_LINES);
diff.into_formatted(move |formatter, store, tree_diff| {
diff_util::show_git_diff(formatter, store, tree_diff, context)
map.insert(
"git",
|language, diagnostics, build_ctx, self_property, function| {
let ([], [context_node]) = function.expect_arguments()?;
let context_property = context_node
.map(|node| {
template_builder::expect_usize_expression(
language,
diagnostics,
build_ctx,
node,
)
})
})
.into_template();
Ok(L::wrap_template(template))
});
map.insert("stat", |language, build_ctx, self_property, function| {
let [width_node] = function.expect_exact_arguments()?;
let width_property =
template_builder::expect_usize_expression(language, build_ctx, width_node)?;
let path_converter = language.path_converter;
let template = (self_property, width_property)
.map(move |(diff, width)| {
diff.into_formatted(move |formatter, store, tree_diff| {
diff_util::show_diff_stat(formatter, store, tree_diff, path_converter, width)
.transpose()?;
let template = (self_property, context_property)
.map(|(diff, context)| {
let context = context.unwrap_or(diff_util::DEFAULT_CONTEXT_LINES);
diff.into_formatted(move |formatter, store, tree_diff| {
diff_util::show_git_diff(formatter, store, tree_diff, context)
})
})
})
.into_template();
Ok(L::wrap_template(template))
});
.into_template();
Ok(L::wrap_template(template))
},
);
map.insert(
"stat",
|language, diagnostics, build_ctx, self_property, function| {
let [width_node] = function.expect_exact_arguments()?;
let width_property = template_builder::expect_usize_expression(
language,
diagnostics,
build_ctx,
width_node,
)?;
let path_converter = language.path_converter;
let template = (self_property, width_property)
.map(move |(diff, width)| {
diff.into_formatted(move |formatter, store, tree_diff| {
diff_util::show_diff_stat(
formatter,
store,
tree_diff,
path_converter,
width,
)
})
})
.into_template();
Ok(L::wrap_template(template))
},
);
map.insert(
"summary",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let path_converter = language.path_converter;
let template = self_property

View file

@ -22,6 +22,7 @@ use crate::template_builder::IntoTemplateProperty;
use crate::template_builder::TemplateLanguage;
use crate::template_parser;
use crate::template_parser::FunctionCallNode;
use crate::template_parser::TemplateDiagnostics;
use crate::template_parser::TemplateParseResult;
use crate::templater::Template;
use crate::templater::TemplateProperty;
@ -86,15 +87,17 @@ impl<'a, C: 'a> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> {
fn build_function(
&self,
diagnostics: &mut TemplateDiagnostics,
build_ctx: &BuildContext<Self::Property>,
function: &FunctionCallNode,
) -> TemplateParseResult<Self::Property> {
let table = &self.build_fn_table.core;
table.build_function(self, build_ctx, function)
table.build_function(self, diagnostics, build_ctx, function)
}
fn build_method(
&self,
diagnostics: &mut TemplateDiagnostics,
build_ctx: &BuildContext<Self::Property>,
property: Self::Property,
function: &FunctionCallNode,
@ -103,7 +106,7 @@ impl<'a, C: 'a> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> {
match property {
GenericTemplatePropertyKind::Core(property) => {
let table = &self.build_fn_table.core;
table.build_method(self, build_ctx, property, function)
table.build_method(self, diagnostics, build_ctx, property, function)
}
GenericTemplatePropertyKind::Self_(property) => {
let table = &self.build_fn_table.keywords;

View file

@ -32,6 +32,7 @@ use crate::template_builder::TemplateBuildMethodFnMap;
use crate::template_builder::TemplateLanguage;
use crate::template_parser;
use crate::template_parser::FunctionCallNode;
use crate::template_parser::TemplateDiagnostics;
use crate::template_parser::TemplateParseResult;
use crate::templater::PlainTextFormattedProperty;
use crate::templater::Template;
@ -87,15 +88,17 @@ impl TemplateLanguage<'static> for OperationTemplateLanguage {
fn build_function(
&self,
diagnostics: &mut TemplateDiagnostics,
build_ctx: &BuildContext<Self::Property>,
function: &FunctionCallNode,
) -> TemplateParseResult<Self::Property> {
let table = &self.build_fn_table.core;
table.build_function(self, build_ctx, function)
table.build_function(self, diagnostics, build_ctx, function)
}
fn build_method(
&self,
diagnostics: &mut TemplateDiagnostics,
build_ctx: &BuildContext<Self::Property>,
property: Self::Property,
function: &FunctionCallNode,
@ -104,17 +107,17 @@ impl TemplateLanguage<'static> for OperationTemplateLanguage {
match property {
OperationTemplatePropertyKind::Core(property) => {
let table = &self.build_fn_table.core;
table.build_method(self, build_ctx, property, function)
table.build_method(self, diagnostics, build_ctx, property, function)
}
OperationTemplatePropertyKind::Operation(property) => {
let table = &self.build_fn_table.operation_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
build(self, build_ctx, property, function)
build(self, diagnostics, build_ctx, property, function)
}
OperationTemplatePropertyKind::OperationId(property) => {
let table = &self.build_fn_table.operation_id_methods;
let build = template_parser::lookup_method(type_name, table, function)?;
build(self, build_ctx, property, function)
build(self, diagnostics, build_ctx, property, function)
}
}
}
@ -236,7 +239,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
let mut map = OperationTemplateBuildMethodFnMap::<Operation>::new();
map.insert(
"current_operation",
|language, _build_ctx, self_property, function| {
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let current_op_id = language.current_op_id.clone();
let out_property = self_property.map(move |op| Some(op.id()) == current_op_id.as_ref());
@ -245,59 +248,74 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
);
map.insert(
"description",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| op.metadata().description.clone());
Ok(L::wrap_string(out_property))
},
);
map.insert("id", |_language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| op.id().clone());
Ok(L::wrap_operation_id(out_property))
});
map.insert("tags", |_language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| {
// TODO: introduce map type
op.metadata()
.tags
.iter()
.map(|(key, value)| format!("{key}: {value}"))
.join("\n")
});
Ok(L::wrap_string(out_property))
});
map.insert(
"id",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| op.id().clone());
Ok(L::wrap_operation_id(out_property))
},
);
map.insert(
"tags",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| {
// TODO: introduce map type
op.metadata()
.tags
.iter()
.map(|(key, value)| format!("{key}: {value}"))
.join("\n")
});
Ok(L::wrap_string(out_property))
},
);
map.insert(
"snapshot",
|_language, _build_ctx, self_property, function| {
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| op.metadata().is_snapshot);
Ok(L::wrap_boolean(out_property))
},
);
map.insert("time", |_language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| TimestampRange {
start: op.metadata().start_time,
end: op.metadata().end_time,
});
Ok(L::wrap_timestamp_range(out_property))
});
map.insert("user", |_language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| {
// TODO: introduce dedicated type and provide accessors?
format!("{}@{}", op.metadata().username, op.metadata().hostname)
});
Ok(L::wrap_string(out_property))
});
map.insert("root", |language, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let root_op_id = language.root_op_id.clone();
let out_property = self_property.map(move |op| op.id() == &root_op_id);
Ok(L::wrap_boolean(out_property))
});
map.insert(
"time",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| TimestampRange {
start: op.metadata().start_time,
end: op.metadata().end_time,
});
Ok(L::wrap_timestamp_range(out_property))
},
);
map.insert(
"user",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.map(|op| {
// TODO: introduce dedicated type and provide accessors?
format!("{}@{}", op.metadata().username, op.metadata().hostname)
});
Ok(L::wrap_string(out_property))
},
);
map.insert(
"root",
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let root_op_id = language.root_op_id.clone();
let out_property = self_property.map(move |op| op.id() == &root_op_id);
Ok(L::wrap_boolean(out_property))
},
);
map
}
@ -312,17 +330,27 @@ fn builtin_operation_id_methods() -> OperationTemplateBuildMethodFnMap<Operation
// Not using maplit::hashmap!{} or custom declarative macro here because
// code completion inside macro is quite restricted.
let mut map = OperationTemplateBuildMethodFnMap::<OperationId>::new();
map.insert("short", |language, build_ctx, self_property, function| {
let ([], [len_node]) = function.expect_arguments()?;
let len_property = len_node
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
.transpose()?;
let out_property = (self_property, len_property).map(|(id, len)| {
let mut hex = id.hex();
hex.truncate(len.unwrap_or(12));
hex
});
Ok(L::wrap_string(out_property))
});
map.insert(
"short",
|language, diagnostics, build_ctx, self_property, function| {
let ([], [len_node]) = function.expect_arguments()?;
let len_property = len_node
.map(|node| {
template_builder::expect_usize_expression(
language,
diagnostics,
build_ctx,
node,
)
})
.transpose()?;
let out_property = (self_property, len_property).map(|(id, len)| {
let mut hex = id.hex();
hex.truncate(len.unwrap_or(12));
hex
});
Ok(L::wrap_string(out_property))
},
);
map
}

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,7 @@ use jj_lib::dsl_util::AliasExpandError;
use jj_lib::dsl_util::AliasExpandableExpression;
use jj_lib::dsl_util::AliasId;
use jj_lib::dsl_util::AliasesMap;
use jj_lib::dsl_util::Diagnostics;
use jj_lib::dsl_util::ExpressionFolder;
use jj_lib::dsl_util::FoldableExpression;
use jj_lib::dsl_util::InvalidArguments;
@ -84,6 +85,9 @@ impl Rule {
}
}
/// Manages diagnostic messages emitted during template parsing and building.
pub type TemplateDiagnostics = Diagnostics<TemplateParseError>;
pub type TemplateParseResult<T> = Result<T, TemplateParseError>;
#[derive(Debug, Error)]