// 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::cmp::max;use std::collections::HashMap;use std::io;use std::rc::Rc; use itertools::Itertools as _;use jj_lib::backend::{ChangeId, CommitId};use jj_lib::commit::Commit;use jj_lib::hex_util::to_reverse_hex;use jj_lib::id_prefix::IdPrefixContext;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::{RefTarget, WorkspaceId};use jj_lib::repo::Repo;use jj_lib::{git, rewrite};use once_cell::unsync::OnceCell; use crate::formatter::Formatter;use crate::template_builder::{ self, merge_fn_map, BuildContext, CoreTemplateBuildFnTable, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateBuildMethodFnMap, TemplateLanguage,};use crate::template_parser::{self, FunctionCallNode, TemplateParseResult};use crate::templater::{ self, IntoTemplate, PlainTextFormattedProperty, Template, TemplateFunction, TemplateProperty, TemplatePropertyFn,};use crate::text_util; pub trait CommitTemplateLanguageExtension { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>;} pub struct CommitTemplateLanguage<'repo> { repo: &'repo dyn Repo, workspace_id: WorkspaceId, id_prefix_context: &'repo IdPrefixContext, build_fn_table: CommitTemplateBuildFnTable<'repo>, keyword_cache: CommitKeywordCache,} impl<'repo> CommitTemplateLanguage<'repo> { /// Sets up environment where commit template will be transformed to /// evaluation tree. pub fn new( repo: &'repo dyn Repo, workspace_id: &WorkspaceId, id_prefix_context: &'repo IdPrefixContext, extension: Option<&dyn CommitTemplateLanguageExtension>, ) -> Self { let mut build_fn_table = CommitTemplateBuildFnTable::builtin(); if let Some(extension) = extension { build_fn_table.merge(extension.build_fn_table()); } CommitTemplateLanguage { repo, workspace_id: workspace_id.clone(), id_prefix_context, build_fn_table, keyword_cache: CommitKeywordCache::default(), } }} impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { type Context = Commit; type Property = CommitTemplatePropertyKind<'repo>; template_builder::impl_core_wrap_property_fns!('repo, CommitTemplatePropertyKind::Core); fn build_self(&self) -> Self::Property { // Commit object is lightweight (a few Arc + CommitId) self.wrap_commit(TemplatePropertyFn(|commit: &Commit| Ok(commit.clone()))) } fn build_method( &self, build_ctx: &BuildContext<Self::Property>, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property> { match property { CommitTemplatePropertyKind::Core(property) => { let table = &self.build_fn_table.core; table.build_method(self, build_ctx, property, function) } CommitTemplatePropertyKind::Commit(property) => { let table = &self.build_fn_table.commit_methods; let build = template_parser::lookup_method("Commit", table, function)?; build(self, build_ctx, property, function) } CommitTemplatePropertyKind::CommitList(property) => { // TODO: migrate to table? template_builder::build_unformattable_list_method( self, build_ctx, property, function, |item| self.wrap_commit(item), ) } CommitTemplatePropertyKind::RefName(property) => { let table = &self.build_fn_table.ref_name_methods; let build = template_parser::lookup_method("RefName", table, function)?; build(self, build_ctx, property, function) } CommitTemplatePropertyKind::RefNameList(property) => { // TODO: migrate to table? template_builder::build_formattable_list_method( self, build_ctx, property, function, |item| self.wrap_ref_name(item), ) } CommitTemplatePropertyKind::CommitOrChangeId(property) => { let table = &self.build_fn_table.commit_or_change_id_methods; let build = template_parser::lookup_method("CommitOrChangeId", table, function)?; build(self, build_ctx, property, function) } CommitTemplatePropertyKind::ShortestIdPrefix(property) => { let table = &self.build_fn_table.shortest_id_prefix_methods; let build = template_parser::lookup_method("ShortestIdPrefix", table, function)?; build(self, 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) -> &dyn Repo { self.repo } pub fn workspace_id(&self) -> &WorkspaceId { &self.workspace_id } pub fn keyword_cache(&self) -> &CommitKeywordCache { &self.keyword_cache } pub fn wrap_commit( &self, property: impl TemplateProperty<Commit, Output = Commit> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::Commit(Box::new(property)) } pub fn wrap_commit_list( &self, property: impl TemplateProperty<Commit, Output = Vec<Commit>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitList(Box::new(property)) } pub fn wrap_ref_name( &self, property: impl TemplateProperty<Commit, Output = RefName> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefName(Box::new(property)) } pub fn wrap_ref_name_list( &self, property: impl TemplateProperty<Commit, Output = Vec<RefName>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefNameList(Box::new(property)) } pub fn wrap_commit_or_change_id( &self, property: impl TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitOrChangeId(Box::new(property)) } pub fn wrap_shortest_id_prefix( &self, property: impl TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::ShortestIdPrefix(Box::new(property)) }} pub enum CommitTemplatePropertyKind<'repo> { Core(CoreTemplatePropertyKind<'repo, Commit>), Commit(Box<dyn TemplateProperty<Commit, Output = Commit> + 'repo>), CommitList(Box<dyn TemplateProperty<Commit, Output = Vec<Commit>> + 'repo>), RefName(Box<dyn TemplateProperty<Commit, Output = RefName> + 'repo>), RefNameList(Box<dyn TemplateProperty<Commit, Output = Vec<RefName>> + 'repo>), CommitOrChangeId(Box<dyn TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo>), ShortestIdPrefix(Box<dyn TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo>),} impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<'repo> { fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Commit, Output = bool> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_boolean(), CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::CommitList(property) => { Some(Box::new(TemplateFunction::new(property, |l| { Ok(!l.is_empty()) }))) } CommitTemplatePropertyKind::RefName(_) => None, CommitTemplatePropertyKind::RefNameList(property) => { Some(Box::new(TemplateFunction::new(property, |l| { Ok(!l.is_empty()) }))) } CommitTemplatePropertyKind::CommitOrChangeId(_) => None, CommitTemplatePropertyKind::ShortestIdPrefix(_) => None, } } fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Commit, Output = i64> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_integer(), _ => None, } } fn try_into_plain_text( self, ) -> Option<Box<dyn TemplateProperty<Commit, 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<Commit> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_template(), CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::CommitList(_) => None, CommitTemplatePropertyKind::RefName(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()) } } }} /// 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, RefName>, pub commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>, pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>,} 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(), } } 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(), } } fn merge(&mut self, extension: CommitTemplateBuildFnTable<'repo>) { let CommitTemplateBuildFnTable { core, commit_methods, ref_name_methods, commit_or_change_id_methods, shortest_id_prefix_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, ); }} #[derive(Debug, Default)]pub struct CommitKeywordCache { // Build index lazily, and Rc to get away from &self lifetime. branches_index: OnceCell<Rc<RefNamesIndex>>, tags_index: OnceCell<Rc<RefNamesIndex>>, git_refs_index: OnceCell<Rc<RefNamesIndex>>,} impl CommitKeywordCache { pub fn branches_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> { self.branches_index .get_or_init(|| Rc::new(build_branches_index(repo))) } pub fn tags_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> { self.tags_index .get_or_init(|| Rc::new(build_ref_names_index(repo.view().tags()))) } pub fn git_refs_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> { self.git_refs_index .get_or_init(|| Rc::new(build_ref_names_index(repo.view().git_refs()))) }} fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Commit> { // Not using maplit::hashmap!{} or custom declarative macro here because // code completion inside macro is quite restricted. let mut map = CommitTemplateBuildMethodFnMap::<Commit>::new(); map.insert( "description", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| { Ok(text_util::complete_newline(commit.description())) }); Ok(language.wrap_string(out_property)) }, ); map.insert( "change_id", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| { Ok(CommitOrChangeId::Change(commit.change_id().to_owned())) }); Ok(language.wrap_commit_or_change_id(out_property)) }, ); map.insert( "commit_id", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| { Ok(CommitOrChangeId::Commit(commit.id().to_owned())) }); Ok(language.wrap_commit_or_change_id(out_property)) }, ); map.insert( "parents", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| Ok(commit.parents())); Ok(language.wrap_commit_list(out_property)) }, ); map.insert("author", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| Ok(commit.author().clone())); Ok(language.wrap_signature(out_property)) }); map.insert( "committer", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| Ok(commit.committer().clone())); Ok(language.wrap_signature(out_property)) }, ); map.insert( "working_copies", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let repo = language.repo; let out_property = TemplateFunction::new(self_property, |commit| { Ok(extract_working_copies(repo, &commit)) }); Ok(language.wrap_string(out_property)) }, ); map.insert( "current_working_copy", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let repo = language.repo; let workspace_id = language.workspace_id.clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(Some(commit.id()) == repo.view().get_wc_commit_id(&workspace_id)) }); Ok(language.wrap_boolean(out_property)) }, ); map.insert( "branches", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.branches_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index .get(commit.id()) .iter() .filter(|ref_name| ref_name.is_local() || !ref_name.synced) .cloned() .collect()) }); Ok(language.wrap_ref_name_list(out_property)) }, ); map.insert( "local_branches", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.branches_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index .get(commit.id()) .iter() .filter(|ref_name| ref_name.is_local()) .cloned() .collect()) }); Ok(language.wrap_ref_name_list(out_property)) }, ); map.insert( "remote_branches", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.branches_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index .get(commit.id()) .iter() .filter(|ref_name| ref_name.is_remote()) .cloned() .collect()) }); Ok(language.wrap_ref_name_list(out_property)) }, ); map.insert("tags", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.tags_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index.get(commit.id()).to_vec()) }); Ok(language.wrap_ref_name_list(out_property)) }); map.insert( "git_refs", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.git_refs_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index.get(commit.id()).to_vec()) }); Ok(language.wrap_ref_name_list(out_property)) }, ); map.insert( "git_head",Lines 1–500 of 863. 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::cmp::max; use std::collections::HashMap; use std::io; use std::rc::Rc; use itertools::Itertools as _; use jj_lib::backend::{ChangeId, CommitId}; use jj_lib::commit::Commit; use jj_lib::hex_util::to_reverse_hex; use jj_lib::id_prefix::IdPrefixContext; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::{RefTarget, WorkspaceId}; use jj_lib::repo::Repo; use jj_lib::{git, rewrite}; use once_cell::unsync::OnceCell; use crate::formatter::Formatter; use crate::template_builder::{ self, merge_fn_map, BuildContext, CoreTemplateBuildFnTable, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateBuildMethodFnMap, TemplateLanguage, }; use crate::template_parser::{self, FunctionCallNode, TemplateParseResult}; use crate::templater::{ self, IntoTemplate, PlainTextFormattedProperty, Template, TemplateFunction, TemplateProperty, TemplatePropertyFn, }; use crate::text_util; pub trait CommitTemplateLanguageExtension { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>; } pub struct CommitTemplateLanguage<'repo> { repo: &'repo dyn Repo, workspace_id: WorkspaceId, id_prefix_context: &'repo IdPrefixContext, build_fn_table: CommitTemplateBuildFnTable<'repo>, keyword_cache: CommitKeywordCache, } impl<'repo> CommitTemplateLanguage<'repo> { /// Sets up environment where commit template will be transformed to /// evaluation tree. pub fn new( repo: &'repo dyn Repo, workspace_id: &WorkspaceId, id_prefix_context: &'repo IdPrefixContext, extension: Option<&dyn CommitTemplateLanguageExtension>, ) -> Self { let mut build_fn_table = CommitTemplateBuildFnTable::builtin(); if let Some(extension) = extension { build_fn_table.merge(extension.build_fn_table()); } CommitTemplateLanguage { repo, workspace_id: workspace_id.clone(), id_prefix_context, build_fn_table, keyword_cache: CommitKeywordCache::default(), } } } impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { type Context = Commit; type Property = CommitTemplatePropertyKind<'repo>; template_builder::impl_core_wrap_property_fns!('repo, CommitTemplatePropertyKind::Core); fn build_self(&self) -> Self::Property { // Commit object is lightweight (a few Arc + CommitId) self.wrap_commit(TemplatePropertyFn(|commit: &Commit| Ok(commit.clone()))) } fn build_method( &self, build_ctx: &BuildContext<Self::Property>, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property> { match property { CommitTemplatePropertyKind::Core(property) => { let table = &self.build_fn_table.core; table.build_method(self, build_ctx, property, function) } CommitTemplatePropertyKind::Commit(property) => { let table = &self.build_fn_table.commit_methods; let build = template_parser::lookup_method("Commit", table, function)?; build(self, build_ctx, property, function) } CommitTemplatePropertyKind::CommitList(property) => { // TODO: migrate to table? template_builder::build_unformattable_list_method( self, build_ctx, property, function, |item| self.wrap_commit(item), ) } CommitTemplatePropertyKind::RefName(property) => { let table = &self.build_fn_table.ref_name_methods; let build = template_parser::lookup_method("RefName", table, function)?; build(self, build_ctx, property, function) } CommitTemplatePropertyKind::RefNameList(property) => { // TODO: migrate to table? template_builder::build_formattable_list_method( self, build_ctx, property, function, |item| self.wrap_ref_name(item), ) } CommitTemplatePropertyKind::CommitOrChangeId(property) => { let table = &self.build_fn_table.commit_or_change_id_methods; let build = template_parser::lookup_method("CommitOrChangeId", table, function)?; build(self, build_ctx, property, function) } CommitTemplatePropertyKind::ShortestIdPrefix(property) => { let table = &self.build_fn_table.shortest_id_prefix_methods; let build = template_parser::lookup_method("ShortestIdPrefix", table, function)?; build(self, 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) -> &dyn Repo { self.repo } pub fn workspace_id(&self) -> &WorkspaceId { &self.workspace_id } pub fn keyword_cache(&self) -> &CommitKeywordCache { &self.keyword_cache } pub fn wrap_commit( &self, property: impl TemplateProperty<Commit, Output = Commit> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::Commit(Box::new(property)) } pub fn wrap_commit_list( &self, property: impl TemplateProperty<Commit, Output = Vec<Commit>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitList(Box::new(property)) } pub fn wrap_ref_name( &self, property: impl TemplateProperty<Commit, Output = RefName> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefName(Box::new(property)) } pub fn wrap_ref_name_list( &self, property: impl TemplateProperty<Commit, Output = Vec<RefName>> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::RefNameList(Box::new(property)) } pub fn wrap_commit_or_change_id( &self, property: impl TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitOrChangeId(Box::new(property)) } pub fn wrap_shortest_id_prefix( &self, property: impl TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::ShortestIdPrefix(Box::new(property)) } } pub enum CommitTemplatePropertyKind<'repo> { Core(CoreTemplatePropertyKind<'repo, Commit>), Commit(Box<dyn TemplateProperty<Commit, Output = Commit> + 'repo>), CommitList(Box<dyn TemplateProperty<Commit, Output = Vec<Commit>> + 'repo>), RefName(Box<dyn TemplateProperty<Commit, Output = RefName> + 'repo>), RefNameList(Box<dyn TemplateProperty<Commit, Output = Vec<RefName>> + 'repo>), CommitOrChangeId(Box<dyn TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo>), ShortestIdPrefix(Box<dyn TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo>), } impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<'repo> { fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Commit, Output = bool> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_boolean(), CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::CommitList(property) => { Some(Box::new(TemplateFunction::new(property, |l| { Ok(!l.is_empty()) }))) } CommitTemplatePropertyKind::RefName(_) => None, CommitTemplatePropertyKind::RefNameList(property) => { Some(Box::new(TemplateFunction::new(property, |l| { Ok(!l.is_empty()) }))) } CommitTemplatePropertyKind::CommitOrChangeId(_) => None, CommitTemplatePropertyKind::ShortestIdPrefix(_) => None, } } fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Commit, Output = i64> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_integer(), _ => None, } } fn try_into_plain_text( self, ) -> Option<Box<dyn TemplateProperty<Commit, 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<Commit> + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_template(), CommitTemplatePropertyKind::Commit(_) => None, CommitTemplatePropertyKind::CommitList(_) => None, CommitTemplatePropertyKind::RefName(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()) } } } } /// 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, RefName>, pub commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>, pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>, } 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(), } } 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(), } } fn merge(&mut self, extension: CommitTemplateBuildFnTable<'repo>) { let CommitTemplateBuildFnTable { core, commit_methods, ref_name_methods, commit_or_change_id_methods, shortest_id_prefix_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, ); } } #[derive(Debug, Default)] pub struct CommitKeywordCache { // Build index lazily, and Rc to get away from &self lifetime. branches_index: OnceCell<Rc<RefNamesIndex>>, tags_index: OnceCell<Rc<RefNamesIndex>>, git_refs_index: OnceCell<Rc<RefNamesIndex>>, } impl CommitKeywordCache { pub fn branches_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> { self.branches_index .get_or_init(|| Rc::new(build_branches_index(repo))) } pub fn tags_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> { self.tags_index .get_or_init(|| Rc::new(build_ref_names_index(repo.view().tags()))) } pub fn git_refs_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> { self.git_refs_index .get_or_init(|| Rc::new(build_ref_names_index(repo.view().git_refs()))) } } fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Commit> { // Not using maplit::hashmap!{} or custom declarative macro here because // code completion inside macro is quite restricted. let mut map = CommitTemplateBuildMethodFnMap::<Commit>::new(); map.insert( "description", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| { Ok(text_util::complete_newline(commit.description())) }); Ok(language.wrap_string(out_property)) }, ); map.insert( "change_id", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| { Ok(CommitOrChangeId::Change(commit.change_id().to_owned())) }); Ok(language.wrap_commit_or_change_id(out_property)) }, ); map.insert( "commit_id", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| { Ok(CommitOrChangeId::Commit(commit.id().to_owned())) }); Ok(language.wrap_commit_or_change_id(out_property)) }, ); map.insert( "parents", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| Ok(commit.parents())); Ok(language.wrap_commit_list(out_property)) }, ); map.insert("author", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| Ok(commit.author().clone())); Ok(language.wrap_signature(out_property)) }); map.insert( "committer", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |commit| Ok(commit.committer().clone())); Ok(language.wrap_signature(out_property)) }, ); map.insert( "working_copies", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let repo = language.repo; let out_property = TemplateFunction::new(self_property, |commit| { Ok(extract_working_copies(repo, &commit)) }); Ok(language.wrap_string(out_property)) }, ); map.insert( "current_working_copy", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let repo = language.repo; let workspace_id = language.workspace_id.clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(Some(commit.id()) == repo.view().get_wc_commit_id(&workspace_id)) }); Ok(language.wrap_boolean(out_property)) }, ); map.insert( "branches", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.branches_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index .get(commit.id()) .iter() .filter(|ref_name| ref_name.is_local() || !ref_name.synced) .cloned() .collect()) }); Ok(language.wrap_ref_name_list(out_property)) }, ); map.insert( "local_branches", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.branches_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index .get(commit.id()) .iter() .filter(|ref_name| ref_name.is_local()) .cloned() .collect()) }); Ok(language.wrap_ref_name_list(out_property)) }, ); map.insert( "remote_branches", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.branches_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index .get(commit.id()) .iter() .filter(|ref_name| ref_name.is_remote()) .cloned() .collect()) }); Ok(language.wrap_ref_name_list(out_property)) }, ); map.insert("tags", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.tags_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index.get(commit.id()).to_vec()) }); Ok(language.wrap_ref_name_list(out_property)) }); map.insert( "git_refs", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let index = language.keyword_cache.git_refs_index(language.repo).clone(); let out_property = TemplateFunction::new(self_property, move |commit| { Ok(index.get(commit.id()).to_vec()) }); Ok(language.wrap_ref_name_list(out_property)) }, ); map.insert( "git_head",