// Copyright 2020-2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::any::Any;use std::cmp::max;use std::collections::HashMap;use std::io;use std::rc::Rc; use futures::stream::BoxStream;use itertools::Itertools as _;use jj_lib::backend::BackendResult;use jj_lib::backend::ChangeId;use jj_lib::backend::CommitId;use jj_lib::commit::Commit;use jj_lib::copies::CopiesTreeDiffEntry;use jj_lib::copies::CopyRecords;use jj_lib::extensions_map::ExtensionsMap;use jj_lib::fileset;use jj_lib::fileset::FilesetDiagnostics;use jj_lib::fileset::FilesetExpression;use jj_lib::id_prefix::IdPrefixContext;use jj_lib::id_prefix::IdPrefixIndex;use jj_lib::matchers::Matcher;use jj_lib::merged_tree::MergedTree;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::RefTarget;use jj_lib::op_store::RemoteRef;use jj_lib::op_store::WorkspaceId;use jj_lib::repo::Repo;use jj_lib::repo_path::RepoPathUiConverter;use jj_lib::revset;use jj_lib::revset::Revset;use jj_lib::revset::RevsetContainingFn;use jj_lib::revset::RevsetDiagnostics;use jj_lib::revset::RevsetModifier;use jj_lib::revset::RevsetParseContext;use jj_lib::revset::UserRevsetExpression;use jj_lib::store::Store;use once_cell::unsync::OnceCell; use crate::diff_util;use crate::formatter::Formatter;use crate::revset_util;use crate::template_builder;use crate::template_builder::merge_fn_map;use crate::template_builder::BuildContext;use crate::template_builder::CoreTemplateBuildFnTable;use crate::template_builder::CoreTemplatePropertyKind;use crate::template_builder::IntoTemplateProperty;use crate::template_builder::TemplateBuildMethodFnMap;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;use crate::templater::PlainTextFormattedProperty;use crate::templater::SizeHint;use crate::templater::Template;use crate::templater::TemplateFormatter;use crate::templater::TemplateProperty;use crate::templater::TemplatePropertyError;use crate::templater::TemplatePropertyExt as _;use crate::text_util; pub trait CommitTemplateLanguageExtension { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>; fn build_cache_extensions(&self, extensions: &mut ExtensionsMap);} pub struct CommitTemplateLanguage<'repo> { repo: &'repo dyn Repo, path_converter: &'repo RepoPathUiConverter, workspace_id: WorkspaceId, // RevsetParseContext doesn't borrow a repo, but we'll need 'repo lifetime // anyway to capture it to evaluate dynamically-constructed user expression // such as `revset("ancestors(" ++ commit_id ++ ")")`. // TODO: Maybe refactor context structs? RepoPathUiConverter and WorkspaceId // are contained in RevsetParseContext for example. revset_parse_context: RevsetParseContext<'repo>, id_prefix_context: &'repo IdPrefixContext, immutable_expression: Rc<UserRevsetExpression>, build_fn_table: CommitTemplateBuildFnTable<'repo>, keyword_cache: CommitKeywordCache<'repo>, cache_extensions: ExtensionsMap,} impl<'repo> CommitTemplateLanguage<'repo> { /// Sets up environment where commit template will be transformed to /// evaluation tree. pub fn new( repo: &'repo dyn Repo, path_converter: &'repo RepoPathUiConverter, workspace_id: &WorkspaceId, revset_parse_context: RevsetParseContext<'repo>, id_prefix_context: &'repo IdPrefixContext, immutable_expression: Rc<UserRevsetExpression>, extensions: &[impl AsRef<dyn CommitTemplateLanguageExtension>], ) -> Self { let mut build_fn_table = CommitTemplateBuildFnTable::builtin(); let mut cache_extensions = ExtensionsMap::empty(); for extension in extensions { build_fn_table.merge(extension.as_ref().build_fn_table()); extension .as_ref() .build_cache_extensions(&mut cache_extensions); } CommitTemplateLanguage { repo, path_converter, workspace_id: workspace_id.clone(), revset_parse_context, id_prefix_context, immutable_expression, build_fn_table, keyword_cache: CommitKeywordCache::default(), cache_extensions, } }} impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { type Property = CommitTemplatePropertyKind<'repo>; template_builder::impl_core_wrap_property_fns!('repo, CommitTemplatePropertyKind::Core); 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, diagnostics, build_ctx, function) } fn build_method( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property> { let type_name = property.type_name(); match property { CommitTemplatePropertyKind::Core(property) => { let table = &self.build_fn_table.core; 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, 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, 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, Self::wrap_commit, ) } CommitTemplatePropertyKind::RefName(property) => { let table = &self.build_fn_table.ref_name_methods; let build = template_parser::lookup_method(type_name, table, 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, 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, Self::wrap_ref_name, ) } 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, 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, 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, diagnostics, build_ctx, property, function) } } }} // If we need to add multiple languages that support Commit types, this can be// turned into a trait which extends TemplateLanguage.impl<'repo> CommitTemplateLanguage<'repo> { pub fn repo(&self) -> &'repo dyn Repo { self.repo } pub fn workspace_id(&self) -> &WorkspaceId { &self.workspace_id } pub fn keyword_cache(&self) -> &CommitKeywordCache<'repo> { &self.keyword_cache } pub fn cache_extension<T: Any>(&self) -> Option<&T> { self.cache_extensions.get::<T>() } pub fn wrap_commit( property: impl TemplateProperty<Output = Commit> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::Commit(Box::new(property)) } pub fn wrap_commit_opt( property: impl TemplateProperty<Output = Option<Commit>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitOpt(Box::new(property)) } pub fn wrap_commit_list( property: impl TemplateProperty<Output = Vec<Commit>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitList(Box::new(property)) } pub fn wrap_ref_name( property: impl TemplateProperty<Output = Rc<RefName>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefName(Box::new(property)) } pub fn wrap_ref_name_opt( property: impl TemplateProperty<Output = Option<Rc<RefName>>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefNameOpt(Box::new(property)) } pub fn wrap_ref_name_list( property: impl TemplateProperty<Output = Vec<Rc<RefName>>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefNameList(Box::new(property)) } pub fn wrap_commit_or_change_id( property: impl TemplateProperty<Output = CommitOrChangeId> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitOrChangeId(Box::new(property)) } pub fn wrap_shortest_id_prefix( property: impl TemplateProperty<Output = ShortestIdPrefix> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::ShortestIdPrefix(Box::new(property)) } pub fn wrap_tree_diff( property: impl TemplateProperty<Output = TreeDiff> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::TreeDiff(Box::new(property)) }} pub enum CommitTemplatePropertyKind<'repo> { Core(CoreTemplatePropertyKind<'repo>), Commit(Box<dyn TemplateProperty<Output = Commit> + 'repo>), CommitOpt(Box<dyn TemplateProperty<Output = Option<Commit>> + 'repo>), CommitList(Box<dyn TemplateProperty<Output = Vec<Commit>> + 'repo>), RefName(Box<dyn TemplateProperty<Output = Rc<RefName>> + 'repo>), RefNameOpt(Box<dyn TemplateProperty<Output = Option<Rc<RefName>>> + 'repo>), RefNameList(Box<dyn TemplateProperty<Output = Vec<Rc<RefName>>> + 'repo>), CommitOrChangeId(Box<dyn TemplateProperty<Output = CommitOrChangeId> + 'repo>), ShortestIdPrefix(Box<dyn TemplateProperty<Output = ShortestIdPrefix> + 'repo>), TreeDiff(Box<dyn TemplateProperty<Output = TreeDiff> + 'repo>),} impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { fn type_name(&self) -> &'static str { match self { CommitTemplatePropertyKind::Core(property) => property.type_name(), CommitTemplatePropertyKind::Commit(_) => "Commit", CommitTemplatePropertyKind::CommitOpt(_) => "Option<Commit>", CommitTemplatePropertyKind::CommitList(_) => "List<Commit>", CommitTemplatePropertyKind::RefName(_) => "RefName", CommitTemplatePropertyKind::RefNameOpt(_) => "Option<RefName>", CommitTemplatePropertyKind::RefNameList(_) => "List<RefName>", CommitTemplatePropertyKind::CommitOrChangeId(_) => "CommitOrChangeId", CommitTemplatePropertyKind::ShortestIdPrefix(_) => "ShortestIdPrefix", CommitTemplatePropertyKind::TreeDiff(_) => "TreeDiff", } } fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_boolean(), CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::CommitOpt(property) => { Some(Box::new(property.map(|opt| opt.is_some()))) } CommitTemplatePropertyKind::CommitList(property) => { Some(Box::new(property.map(|l| !l.is_empty()))) } CommitTemplatePropertyKind::RefName(_) => None, CommitTemplatePropertyKind::RefNameOpt(property) => { Some(Box::new(property.map(|opt| opt.is_some()))) } CommitTemplatePropertyKind::RefNameList(property) => { Some(Box::new(property.map(|l| !l.is_empty()))) } CommitTemplatePropertyKind::CommitOrChangeId(_) => None, CommitTemplatePropertyKind::ShortestIdPrefix(_) => None, // TODO: boolean cast could be implemented, but explicit // diff.empty() method might be better. CommitTemplatePropertyKind::TreeDiff(_) => None, } } fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Output = i64> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_integer(), _ => None, } } fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<Output = String> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_plain_text(), _ => { let template = self.try_into_template()?; Some(Box::new(PlainTextFormattedProperty::new(template))) } } } fn try_into_template(self) -> Option<Box<dyn Template + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_template(), CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::CommitOpt(_) => None, CommitTemplatePropertyKind::CommitList(_) => None, CommitTemplatePropertyKind::RefName(property) => Some(property.into_template()), CommitTemplatePropertyKind::RefNameOpt(property) => Some(property.into_template()), CommitTemplatePropertyKind::RefNameList(property) => Some(property.into_template()), CommitTemplatePropertyKind::CommitOrChangeId(property) => { Some(property.into_template()) } CommitTemplatePropertyKind::ShortestIdPrefix(property) => { Some(property.into_template()) } CommitTemplatePropertyKind::TreeDiff(_) => None, } } fn try_into_eq(self, other: Self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'repo>> { match (self, other) { (CommitTemplatePropertyKind::Core(lhs), CommitTemplatePropertyKind::Core(rhs)) => { lhs.try_into_eq(rhs) } (CommitTemplatePropertyKind::Core(_), _) => None, (CommitTemplatePropertyKind::Commit(_), _) => None, (CommitTemplatePropertyKind::CommitOpt(_), _) => None, (CommitTemplatePropertyKind::CommitList(_), _) => None, (CommitTemplatePropertyKind::RefName(_), _) => None, (CommitTemplatePropertyKind::RefNameOpt(_), _) => None, (CommitTemplatePropertyKind::RefNameList(_), _) => None, (CommitTemplatePropertyKind::CommitOrChangeId(_), _) => None, (CommitTemplatePropertyKind::ShortestIdPrefix(_), _) => None, (CommitTemplatePropertyKind::TreeDiff(_), _) => None, } }} /// Table of functions that translate method call node of self type `T`.pub type CommitTemplateBuildMethodFnMap<'repo, T> = TemplateBuildMethodFnMap<'repo, CommitTemplateLanguage<'repo>, T>; /// Symbol table of methods available in the commit template.pub struct CommitTemplateBuildFnTable<'repo> { pub core: CoreTemplateBuildFnTable<'repo, CommitTemplateLanguage<'repo>>, pub commit_methods: CommitTemplateBuildMethodFnMap<'repo, Commit>, pub ref_name_methods: CommitTemplateBuildMethodFnMap<'repo, Rc<RefName>>, pub commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>, pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>, pub tree_diff_methods: CommitTemplateBuildMethodFnMap<'repo, TreeDiff>,} impl<'repo> CommitTemplateBuildFnTable<'repo> { /// Creates new symbol table containing the builtin methods. fn builtin() -> Self { CommitTemplateBuildFnTable { core: CoreTemplateBuildFnTable::builtin(), commit_methods: builtin_commit_methods(), ref_name_methods: builtin_ref_name_methods(), commit_or_change_id_methods: builtin_commit_or_change_id_methods(), shortest_id_prefix_methods: builtin_shortest_id_prefix_methods(), tree_diff_methods: builtin_tree_diff_methods(), } } pub fn empty() -> Self { CommitTemplateBuildFnTable { core: CoreTemplateBuildFnTable::empty(), commit_methods: HashMap::new(), ref_name_methods: HashMap::new(), commit_or_change_id_methods: HashMap::new(), shortest_id_prefix_methods: HashMap::new(), tree_diff_methods: HashMap::new(), } } fn merge(&mut self, extension: CommitTemplateBuildFnTable<'repo>) { let CommitTemplateBuildFnTable { core, commit_methods, ref_name_methods, commit_or_change_id_methods, shortest_id_prefix_methods, tree_diff_methods, } = extension; self.core.merge(core); merge_fn_map(&mut self.commit_methods, commit_methods); merge_fn_map(&mut self.ref_name_methods, ref_name_methods); merge_fn_map( &mut self.commit_or_change_id_methods, commit_or_change_id_methods, ); merge_fn_map( &mut self.shortest_id_prefix_methods, shortest_id_prefix_methods, ); merge_fn_map(&mut self.tree_diff_methods, tree_diff_methods); }} #[derive(Default)]pub struct CommitKeywordCache<'repo> { // Build index lazily, and Rc to get away from &self lifetime. bookmarks_index: OnceCell<Rc<RefNamesIndex>>, tags_index: OnceCell<Rc<RefNamesIndex>>, git_refs_index: OnceCell<Rc<RefNamesIndex>>, is_immutable_fn: OnceCell<Rc<RevsetContainingFn<'repo>>>,} impl<'repo> CommitKeywordCache<'repo> { pub fn bookmarks_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> {Lines 1–500 of 1635. Load more
// Copyright 2020-2023 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::any::Any; use std::cmp::max; use std::collections::HashMap; use std::io; use std::rc::Rc; use futures::stream::BoxStream; use itertools::Itertools as _; use jj_lib::backend::BackendResult; use jj_lib::backend::ChangeId; use jj_lib::backend::CommitId; use jj_lib::commit::Commit; use jj_lib::copies::CopiesTreeDiffEntry; use jj_lib::copies::CopyRecords; use jj_lib::extensions_map::ExtensionsMap; use jj_lib::fileset; use jj_lib::fileset::FilesetDiagnostics; use jj_lib::fileset::FilesetExpression; use jj_lib::id_prefix::IdPrefixContext; use jj_lib::id_prefix::IdPrefixIndex; use jj_lib::matchers::Matcher; use jj_lib::merged_tree::MergedTree; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::RefTarget; use jj_lib::op_store::RemoteRef; use jj_lib::op_store::WorkspaceId; use jj_lib::repo::Repo; use jj_lib::repo_path::RepoPathUiConverter; use jj_lib::revset; use jj_lib::revset::Revset; use jj_lib::revset::RevsetContainingFn; use jj_lib::revset::RevsetDiagnostics; use jj_lib::revset::RevsetModifier; use jj_lib::revset::RevsetParseContext; use jj_lib::revset::UserRevsetExpression; use jj_lib::store::Store; use once_cell::unsync::OnceCell; use crate::diff_util; use crate::formatter::Formatter; use crate::revset_util; use crate::template_builder; use crate::template_builder::merge_fn_map; use crate::template_builder::BuildContext; use crate::template_builder::CoreTemplateBuildFnTable; use crate::template_builder::CoreTemplatePropertyKind; use crate::template_builder::IntoTemplateProperty; use crate::template_builder::TemplateBuildMethodFnMap; 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; use crate::templater::PlainTextFormattedProperty; use crate::templater::SizeHint; use crate::templater::Template; use crate::templater::TemplateFormatter; use crate::templater::TemplateProperty; use crate::templater::TemplatePropertyError; use crate::templater::TemplatePropertyExt as _; use crate::text_util; pub trait CommitTemplateLanguageExtension { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>; fn build_cache_extensions(&self, extensions: &mut ExtensionsMap); } pub struct CommitTemplateLanguage<'repo> { repo: &'repo dyn Repo, path_converter: &'repo RepoPathUiConverter, workspace_id: WorkspaceId, // RevsetParseContext doesn't borrow a repo, but we'll need 'repo lifetime // anyway to capture it to evaluate dynamically-constructed user expression // such as `revset("ancestors(" ++ commit_id ++ ")")`. // TODO: Maybe refactor context structs? RepoPathUiConverter and WorkspaceId // are contained in RevsetParseContext for example. revset_parse_context: RevsetParseContext<'repo>, id_prefix_context: &'repo IdPrefixContext, immutable_expression: Rc<UserRevsetExpression>, build_fn_table: CommitTemplateBuildFnTable<'repo>, keyword_cache: CommitKeywordCache<'repo>, cache_extensions: ExtensionsMap, } impl<'repo> CommitTemplateLanguage<'repo> { /// Sets up environment where commit template will be transformed to /// evaluation tree. pub fn new( repo: &'repo dyn Repo, path_converter: &'repo RepoPathUiConverter, workspace_id: &WorkspaceId, revset_parse_context: RevsetParseContext<'repo>, id_prefix_context: &'repo IdPrefixContext, immutable_expression: Rc<UserRevsetExpression>, extensions: &[impl AsRef<dyn CommitTemplateLanguageExtension>], ) -> Self { let mut build_fn_table = CommitTemplateBuildFnTable::builtin(); let mut cache_extensions = ExtensionsMap::empty(); for extension in extensions { build_fn_table.merge(extension.as_ref().build_fn_table()); extension .as_ref() .build_cache_extensions(&mut cache_extensions); } CommitTemplateLanguage { repo, path_converter, workspace_id: workspace_id.clone(), revset_parse_context, id_prefix_context, immutable_expression, build_fn_table, keyword_cache: CommitKeywordCache::default(), cache_extensions, } } } impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { type Property = CommitTemplatePropertyKind<'repo>; template_builder::impl_core_wrap_property_fns!('repo, CommitTemplatePropertyKind::Core); 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, diagnostics, build_ctx, function) } fn build_method( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property> { let type_name = property.type_name(); match property { CommitTemplatePropertyKind::Core(property) => { let table = &self.build_fn_table.core; 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, 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, 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, Self::wrap_commit, ) } CommitTemplatePropertyKind::RefName(property) => { let table = &self.build_fn_table.ref_name_methods; let build = template_parser::lookup_method(type_name, table, 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, 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, Self::wrap_ref_name, ) } 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, 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, 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, diagnostics, build_ctx, property, function) } } } } // If we need to add multiple languages that support Commit types, this can be // turned into a trait which extends TemplateLanguage. impl<'repo> CommitTemplateLanguage<'repo> { pub fn repo(&self) -> &'repo dyn Repo { self.repo } pub fn workspace_id(&self) -> &WorkspaceId { &self.workspace_id } pub fn keyword_cache(&self) -> &CommitKeywordCache<'repo> { &self.keyword_cache } pub fn cache_extension<T: Any>(&self) -> Option<&T> { self.cache_extensions.get::<T>() } pub fn wrap_commit( property: impl TemplateProperty<Output = Commit> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::Commit(Box::new(property)) } pub fn wrap_commit_opt( property: impl TemplateProperty<Output = Option<Commit>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitOpt(Box::new(property)) } pub fn wrap_commit_list( property: impl TemplateProperty<Output = Vec<Commit>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitList(Box::new(property)) } pub fn wrap_ref_name( property: impl TemplateProperty<Output = Rc<RefName>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefName(Box::new(property)) } pub fn wrap_ref_name_opt( property: impl TemplateProperty<Output = Option<Rc<RefName>>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefNameOpt(Box::new(property)) } pub fn wrap_ref_name_list( property: impl TemplateProperty<Output = Vec<Rc<RefName>>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefNameList(Box::new(property)) } pub fn wrap_commit_or_change_id( property: impl TemplateProperty<Output = CommitOrChangeId> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitOrChangeId(Box::new(property)) } pub fn wrap_shortest_id_prefix( property: impl TemplateProperty<Output = ShortestIdPrefix> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::ShortestIdPrefix(Box::new(property)) } pub fn wrap_tree_diff( property: impl TemplateProperty<Output = TreeDiff> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::TreeDiff(Box::new(property)) } } pub enum CommitTemplatePropertyKind<'repo> { Core(CoreTemplatePropertyKind<'repo>), Commit(Box<dyn TemplateProperty<Output = Commit> + 'repo>), CommitOpt(Box<dyn TemplateProperty<Output = Option<Commit>> + 'repo>), CommitList(Box<dyn TemplateProperty<Output = Vec<Commit>> + 'repo>), RefName(Box<dyn TemplateProperty<Output = Rc<RefName>> + 'repo>), RefNameOpt(Box<dyn TemplateProperty<Output = Option<Rc<RefName>>> + 'repo>), RefNameList(Box<dyn TemplateProperty<Output = Vec<Rc<RefName>>> + 'repo>), CommitOrChangeId(Box<dyn TemplateProperty<Output = CommitOrChangeId> + 'repo>), ShortestIdPrefix(Box<dyn TemplateProperty<Output = ShortestIdPrefix> + 'repo>), TreeDiff(Box<dyn TemplateProperty<Output = TreeDiff> + 'repo>), } impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { fn type_name(&self) -> &'static str { match self { CommitTemplatePropertyKind::Core(property) => property.type_name(), CommitTemplatePropertyKind::Commit(_) => "Commit", CommitTemplatePropertyKind::CommitOpt(_) => "Option<Commit>", CommitTemplatePropertyKind::CommitList(_) => "List<Commit>", CommitTemplatePropertyKind::RefName(_) => "RefName", CommitTemplatePropertyKind::RefNameOpt(_) => "Option<RefName>", CommitTemplatePropertyKind::RefNameList(_) => "List<RefName>", CommitTemplatePropertyKind::CommitOrChangeId(_) => "CommitOrChangeId", CommitTemplatePropertyKind::ShortestIdPrefix(_) => "ShortestIdPrefix", CommitTemplatePropertyKind::TreeDiff(_) => "TreeDiff", } } fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_boolean(), CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::CommitOpt(property) => { Some(Box::new(property.map(|opt| opt.is_some()))) } CommitTemplatePropertyKind::CommitList(property) => { Some(Box::new(property.map(|l| !l.is_empty()))) } CommitTemplatePropertyKind::RefName(_) => None, CommitTemplatePropertyKind::RefNameOpt(property) => { Some(Box::new(property.map(|opt| opt.is_some()))) } CommitTemplatePropertyKind::RefNameList(property) => { Some(Box::new(property.map(|l| !l.is_empty()))) } CommitTemplatePropertyKind::CommitOrChangeId(_) => None, CommitTemplatePropertyKind::ShortestIdPrefix(_) => None, // TODO: boolean cast could be implemented, but explicit // diff.empty() method might be better. CommitTemplatePropertyKind::TreeDiff(_) => None, } } fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Output = i64> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_integer(), _ => None, } } fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<Output = String> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_plain_text(), _ => { let template = self.try_into_template()?; Some(Box::new(PlainTextFormattedProperty::new(template))) } } } fn try_into_template(self) -> Option<Box<dyn Template + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_template(), CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::CommitOpt(_) => None, CommitTemplatePropertyKind::CommitList(_) => None, CommitTemplatePropertyKind::RefName(property) => Some(property.into_template()), CommitTemplatePropertyKind::RefNameOpt(property) => Some(property.into_template()), CommitTemplatePropertyKind::RefNameList(property) => Some(property.into_template()), CommitTemplatePropertyKind::CommitOrChangeId(property) => { Some(property.into_template()) } CommitTemplatePropertyKind::ShortestIdPrefix(property) => { Some(property.into_template()) } CommitTemplatePropertyKind::TreeDiff(_) => None, } } fn try_into_eq(self, other: Self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'repo>> { match (self, other) { (CommitTemplatePropertyKind::Core(lhs), CommitTemplatePropertyKind::Core(rhs)) => { lhs.try_into_eq(rhs) } (CommitTemplatePropertyKind::Core(_), _) => None, (CommitTemplatePropertyKind::Commit(_), _) => None, (CommitTemplatePropertyKind::CommitOpt(_), _) => None, (CommitTemplatePropertyKind::CommitList(_), _) => None, (CommitTemplatePropertyKind::RefName(_), _) => None, (CommitTemplatePropertyKind::RefNameOpt(_), _) => None, (CommitTemplatePropertyKind::RefNameList(_), _) => None, (CommitTemplatePropertyKind::CommitOrChangeId(_), _) => None, (CommitTemplatePropertyKind::ShortestIdPrefix(_), _) => None, (CommitTemplatePropertyKind::TreeDiff(_), _) => None, } } } /// Table of functions that translate method call node of self type `T`. pub type CommitTemplateBuildMethodFnMap<'repo, T> = TemplateBuildMethodFnMap<'repo, CommitTemplateLanguage<'repo>, T>; /// Symbol table of methods available in the commit template. pub struct CommitTemplateBuildFnTable<'repo> { pub core: CoreTemplateBuildFnTable<'repo, CommitTemplateLanguage<'repo>>, pub commit_methods: CommitTemplateBuildMethodFnMap<'repo, Commit>, pub ref_name_methods: CommitTemplateBuildMethodFnMap<'repo, Rc<RefName>>, pub commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>, pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>, pub tree_diff_methods: CommitTemplateBuildMethodFnMap<'repo, TreeDiff>, } impl<'repo> CommitTemplateBuildFnTable<'repo> { /// Creates new symbol table containing the builtin methods. fn builtin() -> Self { CommitTemplateBuildFnTable { core: CoreTemplateBuildFnTable::builtin(), commit_methods: builtin_commit_methods(), ref_name_methods: builtin_ref_name_methods(), commit_or_change_id_methods: builtin_commit_or_change_id_methods(), shortest_id_prefix_methods: builtin_shortest_id_prefix_methods(), tree_diff_methods: builtin_tree_diff_methods(), } } pub fn empty() -> Self { CommitTemplateBuildFnTable { core: CoreTemplateBuildFnTable::empty(), commit_methods: HashMap::new(), ref_name_methods: HashMap::new(), commit_or_change_id_methods: HashMap::new(), shortest_id_prefix_methods: HashMap::new(), tree_diff_methods: HashMap::new(), } } fn merge(&mut self, extension: CommitTemplateBuildFnTable<'repo>) { let CommitTemplateBuildFnTable { core, commit_methods, ref_name_methods, commit_or_change_id_methods, shortest_id_prefix_methods, tree_diff_methods, } = extension; self.core.merge(core); merge_fn_map(&mut self.commit_methods, commit_methods); merge_fn_map(&mut self.ref_name_methods, ref_name_methods); merge_fn_map( &mut self.commit_or_change_id_methods, commit_or_change_id_methods, ); merge_fn_map( &mut self.shortest_id_prefix_methods, shortest_id_prefix_methods, ); merge_fn_map(&mut self.tree_diff_methods, tree_diff_methods); } } #[derive(Default)] pub struct CommitKeywordCache<'repo> { // Build index lazily, and Rc to get away from &self lifetime. bookmarks_index: OnceCell<Rc<RefNamesIndex>>, tags_index: OnceCell<Rc<RefNamesIndex>>, git_refs_index: OnceCell<Rc<RefNamesIndex>>, is_immutable_fn: OnceCell<Rc<RevsetContainingFn<'repo>>>, } impl<'repo> CommitKeywordCache<'repo> { pub fn bookmarks_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> {