Skip to main content

jj_cli/
operation_templater.rs

1// Copyright 2023 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Template environment for `jj op log`.
16
17use std::any::Any;
18use std::cmp::Ordering;
19use std::collections::HashMap;
20use std::io;
21
22use bstr::BString;
23use itertools::Itertools as _;
24use jj_lib::backend::Timestamp;
25use jj_lib::extensions_map::ExtensionsMap;
26use jj_lib::object_id::ObjectId as _;
27use jj_lib::op_store::OperationId;
28use jj_lib::operation::Operation;
29use jj_lib::repo::RepoLoader;
30use jj_lib::settings::UserSettings;
31use pollster::FutureExt as _;
32
33use crate::template_builder;
34use crate::template_builder::BuildContext;
35use crate::template_builder::CoreTemplateBuildFnTable;
36use crate::template_builder::CoreTemplatePropertyKind;
37use crate::template_builder::CoreTemplatePropertyVar;
38use crate::template_builder::TemplateBuildMethodFnMap;
39use crate::template_builder::TemplateLanguage;
40use crate::template_builder::merge_fn_map;
41use crate::template_parser;
42use crate::template_parser::FunctionCallNode;
43use crate::template_parser::TemplateDiagnostics;
44use crate::template_parser::TemplateParseError;
45use crate::template_parser::TemplateParseResult;
46use crate::templater::BoxedAnyProperty;
47use crate::templater::BoxedSerializeProperty;
48use crate::templater::BoxedTemplateProperty;
49use crate::templater::Template;
50use crate::templater::TemplateFormatter;
51use crate::templater::TemplatePropertyExt as _;
52use crate::templater::WrapTemplateProperty;
53
54pub trait OperationTemplateLanguageExtension {
55    fn build_fn_table(&self) -> OperationTemplateLanguageBuildFnTable;
56
57    fn build_cache_extensions(&self, extensions: &mut ExtensionsMap);
58}
59
60/// Global resources needed by [`OperationTemplatePropertyKind`] methods.
61pub trait OperationTemplateEnvironment {
62    fn repo_loader(&self) -> &RepoLoader;
63    fn current_op_id(&self) -> Option<&OperationId>;
64}
65
66/// Template environment for `jj op log`.
67pub struct OperationTemplateLanguage {
68    repo_loader: RepoLoader,
69    current_op_id: Option<OperationId>,
70    build_fn_table: OperationTemplateLanguageBuildFnTable,
71    cache_extensions: ExtensionsMap,
72}
73
74impl OperationTemplateLanguage {
75    /// Sets up environment where operation template will be transformed to
76    /// evaluation tree.
77    pub fn new(
78        repo_loader: &RepoLoader,
79        current_op_id: Option<&OperationId>,
80        extensions: &[impl AsRef<dyn OperationTemplateLanguageExtension>],
81    ) -> Self {
82        let mut build_fn_table = OperationTemplateLanguageBuildFnTable::builtin();
83        let mut cache_extensions = ExtensionsMap::empty();
84
85        for extension in extensions {
86            build_fn_table.merge(extension.as_ref().build_fn_table());
87            extension
88                .as_ref()
89                .build_cache_extensions(&mut cache_extensions);
90        }
91
92        Self {
93            // Clone these to keep lifetime simple
94            repo_loader: repo_loader.clone(),
95            current_op_id: current_op_id.cloned(),
96            build_fn_table,
97            cache_extensions,
98        }
99    }
100}
101
102impl TemplateLanguage<'static> for OperationTemplateLanguage {
103    type Property = OperationTemplateLanguagePropertyKind;
104
105    fn settings(&self) -> &UserSettings {
106        self.repo_loader.settings()
107    }
108
109    fn build_function(
110        &self,
111        diagnostics: &mut TemplateDiagnostics,
112        build_ctx: &BuildContext<Self::Property>,
113        function: &FunctionCallNode,
114    ) -> TemplateParseResult<Self::Property> {
115        let table = &self.build_fn_table.core;
116        table.build_function(self, diagnostics, build_ctx, function)
117    }
118
119    fn build_method(
120        &self,
121        diagnostics: &mut TemplateDiagnostics,
122        build_ctx: &BuildContext<Self::Property>,
123        property: Self::Property,
124        function: &FunctionCallNode,
125    ) -> TemplateParseResult<Self::Property> {
126        match property {
127            OperationTemplateLanguagePropertyKind::Core(property) => {
128                let table = &self.build_fn_table.core;
129                table.build_method(self, diagnostics, build_ctx, property, function)
130            }
131            OperationTemplateLanguagePropertyKind::Operation(property) => {
132                let table = &self.build_fn_table.operation;
133                table.build_method(self, diagnostics, build_ctx, property, function)
134            }
135        }
136    }
137}
138
139impl OperationTemplateEnvironment for OperationTemplateLanguage {
140    fn repo_loader(&self) -> &RepoLoader {
141        &self.repo_loader
142    }
143
144    fn current_op_id(&self) -> Option<&OperationId> {
145        self.current_op_id.as_ref()
146    }
147}
148
149impl OperationTemplateLanguage {
150    pub fn cache_extension<T: Any>(&self) -> Option<&T> {
151        self.cache_extensions.get::<T>()
152    }
153}
154
155/// Wrapper for the operation template property types.
156pub trait OperationTemplatePropertyVar<'a>
157where
158    Self: WrapTemplateProperty<'a, Operation>,
159    Self: WrapTemplateProperty<'a, Option<Operation>>,
160    Self: WrapTemplateProperty<'a, Vec<Operation>>,
161    Self: WrapTemplateProperty<'a, OperationId>,
162{
163}
164
165/// Tagged union of the operation template property types.
166pub enum OperationTemplatePropertyKind<'a> {
167    Operation(BoxedTemplateProperty<'a, Operation>),
168    OperationOpt(BoxedTemplateProperty<'a, Option<Operation>>),
169    OperationList(BoxedTemplateProperty<'a, Vec<Operation>>),
170    OperationId(BoxedTemplateProperty<'a, OperationId>),
171}
172
173/// Implements `WrapTemplateProperty<type>` for operation property types.
174///
175/// Use `impl_operation_property_wrappers!(<'a> Kind<'a> => Operation);` to
176/// implement forwarding conversion.
177macro_rules! impl_operation_property_wrappers {
178    ($($head:tt)+) => {
179        $crate::template_builder::impl_property_wrappers!($($head)+ {
180            Operation(jj_lib::operation::Operation),
181            OperationOpt(Option<jj_lib::operation::Operation>),
182            OperationList(Vec<jj_lib::operation::Operation>),
183            OperationId(jj_lib::op_store::OperationId),
184        });
185    };
186}
187
188pub(crate) use impl_operation_property_wrappers;
189
190impl_operation_property_wrappers!(<'a> OperationTemplatePropertyKind<'a>);
191
192impl<'a> OperationTemplatePropertyKind<'a> {
193    pub fn type_name(&self) -> &'static str {
194        match self {
195            Self::Operation(_) => "Operation",
196            Self::OperationOpt(_) => "Option<Operation>",
197            Self::OperationList(_) => "List<Operation>",
198            Self::OperationId(_) => "OperationId",
199        }
200    }
201
202    pub fn try_into_byte_string(self) -> Result<BoxedTemplateProperty<'a, BString>, Self> {
203        Err(self)
204    }
205
206    pub fn try_into_string(self) -> Result<BoxedTemplateProperty<'a, String>, Self> {
207        Err(self)
208    }
209
210    pub fn try_into_boolean(self) -> Result<BoxedTemplateProperty<'a, bool>, Self> {
211        match self {
212            Self::Operation(_) => Err(self),
213            Self::OperationOpt(property) => Ok(property.map(|opt| opt.is_some()).into_dyn()),
214            Self::OperationList(property) => Ok(property.map(|l| !l.is_empty()).into_dyn()),
215            Self::OperationId(_) => Err(self),
216        }
217    }
218
219    pub fn try_into_integer(self) -> Result<BoxedTemplateProperty<'a, i64>, Self> {
220        Err(self)
221    }
222
223    pub fn try_into_timestamp(self) -> Result<BoxedTemplateProperty<'a, Timestamp>, Self> {
224        Err(self)
225    }
226
227    pub fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> {
228        match self {
229            Self::Operation(property) => Some(property.into_serialize()),
230            Self::OperationOpt(property) => Some(property.into_serialize()),
231            Self::OperationList(property) => Some(property.into_serialize()),
232            Self::OperationId(property) => Some(property.into_serialize()),
233        }
234    }
235
236    pub fn try_into_template(self) -> Option<Box<dyn Template + 'a>> {
237        match self {
238            Self::Operation(_) => None,
239            Self::OperationOpt(_) => None,
240            Self::OperationList(_) => None,
241            Self::OperationId(property) => Some(property.into_template()),
242        }
243    }
244
245    pub fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> {
246        match (self, other) {
247            (Self::Operation(_), _) => None,
248            (Self::OperationOpt(_), _) => None,
249            (Self::OperationList(_), _) => None,
250            (Self::OperationId(_), _) => None,
251        }
252    }
253
254    pub fn try_into_eq_core(
255        self,
256        other: CoreTemplatePropertyKind<'a>,
257    ) -> Option<BoxedTemplateProperty<'a, bool>> {
258        match (self, other) {
259            (Self::Operation(_), _) => None,
260            (Self::OperationOpt(_), _) => None,
261            (Self::OperationList(_), _) => None,
262            (Self::OperationId(_), _) => None,
263        }
264    }
265
266    pub fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> {
267        match (self, other) {
268            (Self::Operation(_), _) => None,
269            (Self::OperationOpt(_), _) => None,
270            (Self::OperationList(_), _) => None,
271            (Self::OperationId(_), _) => None,
272        }
273    }
274
275    pub fn try_into_cmp_core(
276        self,
277        other: CoreTemplatePropertyKind<'a>,
278    ) -> Option<BoxedTemplateProperty<'a, Ordering>> {
279        match (self, other) {
280            (Self::Operation(_), _) => None,
281            (Self::OperationOpt(_), _) => None,
282            (Self::OperationList(_), _) => None,
283            (Self::OperationId(_), _) => None,
284        }
285    }
286}
287
288/// Tagged property types available in [`OperationTemplateLanguage`].
289pub enum OperationTemplateLanguagePropertyKind {
290    Core(CoreTemplatePropertyKind<'static>),
291    Operation(OperationTemplatePropertyKind<'static>),
292}
293
294template_builder::impl_core_property_wrappers!(OperationTemplateLanguagePropertyKind => Core);
295impl_operation_property_wrappers!(OperationTemplateLanguagePropertyKind => Operation);
296
297impl CoreTemplatePropertyVar<'static> for OperationTemplateLanguagePropertyKind {
298    fn wrap_template(template: Box<dyn Template>) -> Self {
299        Self::Core(CoreTemplatePropertyKind::wrap_template(template))
300    }
301
302    fn wrap_any(property: BoxedAnyProperty<'static>) -> Self {
303        Self::Core(CoreTemplatePropertyKind::wrap_any(property))
304    }
305
306    fn wrap_any_list(property: BoxedAnyProperty<'static>) -> Self {
307        Self::Core(CoreTemplatePropertyKind::wrap_any_list(property))
308    }
309
310    fn type_name(&self) -> &'static str {
311        match self {
312            Self::Core(property) => property.type_name(),
313            Self::Operation(property) => property.type_name(),
314        }
315    }
316
317    fn try_into_byte_string(self) -> Result<BoxedTemplateProperty<'static, BString>, Self> {
318        match self {
319            Self::Core(property) => property.try_into_byte_string().map_err(Self::Core),
320            Self::Operation(property) => property.try_into_byte_string().map_err(Self::Operation),
321        }
322    }
323
324    fn try_into_string(self) -> Result<BoxedTemplateProperty<'static, String>, Self> {
325        match self {
326            Self::Core(property) => property.try_into_string().map_err(Self::Core),
327            Self::Operation(property) => property.try_into_string().map_err(Self::Operation),
328        }
329    }
330
331    fn try_into_boolean(self) -> Result<BoxedTemplateProperty<'static, bool>, Self> {
332        match self {
333            Self::Core(property) => property.try_into_boolean().map_err(Self::Core),
334            Self::Operation(property) => property.try_into_boolean().map_err(Self::Operation),
335        }
336    }
337
338    fn try_into_integer(self) -> Result<BoxedTemplateProperty<'static, i64>, Self> {
339        match self {
340            Self::Core(property) => property.try_into_integer().map_err(Self::Core),
341            Self::Operation(property) => property.try_into_integer().map_err(Self::Operation),
342        }
343    }
344
345    fn try_into_timestamp(self) -> Result<BoxedTemplateProperty<'static, Timestamp>, Self> {
346        match self {
347            Self::Core(property) => property.try_into_timestamp().map_err(Self::Core),
348            Self::Operation(property) => property.try_into_timestamp().map_err(Self::Operation),
349        }
350    }
351
352    fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'static>> {
353        match self {
354            Self::Core(property) => property.try_into_serialize(),
355            Self::Operation(property) => property.try_into_serialize(),
356        }
357    }
358
359    fn try_into_template(self) -> Option<Box<dyn Template>> {
360        match self {
361            Self::Core(property) => property.try_into_template(),
362            Self::Operation(property) => property.try_into_template(),
363        }
364    }
365
366    fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'static, bool>> {
367        match (self, other) {
368            (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_eq(rhs),
369            (Self::Core(lhs), Self::Operation(rhs)) => rhs.try_into_eq_core(lhs),
370            (Self::Operation(lhs), Self::Core(rhs)) => lhs.try_into_eq_core(rhs),
371            (Self::Operation(lhs), Self::Operation(rhs)) => lhs.try_into_eq(rhs),
372        }
373    }
374
375    fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'static, Ordering>> {
376        match (self, other) {
377            (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_cmp(rhs),
378            (Self::Core(lhs), Self::Operation(rhs)) => rhs
379                .try_into_cmp_core(lhs)
380                .map(|property| property.map(Ordering::reverse).into_dyn()),
381            (Self::Operation(lhs), Self::Core(rhs)) => lhs.try_into_cmp_core(rhs),
382            (Self::Operation(lhs), Self::Operation(rhs)) => lhs.try_into_cmp(rhs),
383        }
384    }
385}
386
387impl OperationTemplatePropertyVar<'static> for OperationTemplateLanguagePropertyKind {}
388
389/// Symbol table for the operation template property types.
390pub struct OperationTemplateBuildFnTable<'a, L: ?Sized, P = <L as TemplateLanguage<'a>>::Property> {
391    pub operation_methods: TemplateBuildMethodFnMap<'a, L, Operation, P>,
392    pub operation_list_methods: TemplateBuildMethodFnMap<'a, L, Vec<Operation>, P>,
393    pub operation_id_methods: TemplateBuildMethodFnMap<'a, L, OperationId, P>,
394}
395
396impl<L: ?Sized, P> OperationTemplateBuildFnTable<'_, L, P> {
397    pub fn empty() -> Self {
398        Self {
399            operation_methods: HashMap::new(),
400            operation_list_methods: HashMap::new(),
401            operation_id_methods: HashMap::new(),
402        }
403    }
404
405    pub fn merge(&mut self, other: Self) {
406        let Self {
407            operation_methods,
408            operation_list_methods,
409            operation_id_methods,
410        } = other;
411
412        merge_fn_map(&mut self.operation_methods, operation_methods);
413        merge_fn_map(&mut self.operation_list_methods, operation_list_methods);
414        merge_fn_map(&mut self.operation_id_methods, operation_id_methods);
415    }
416}
417
418impl<'a, L> OperationTemplateBuildFnTable<'a, L, L::Property>
419where
420    L: TemplateLanguage<'a> + OperationTemplateEnvironment + ?Sized,
421    L::Property: OperationTemplatePropertyVar<'a>,
422{
423    /// Creates new symbol table containing the builtin methods.
424    pub fn builtin() -> Self {
425        Self {
426            operation_methods: builtin_operation_methods(),
427            operation_list_methods: template_builder::builtin_unformattable_list_methods(),
428            operation_id_methods: builtin_operation_id_methods(),
429        }
430    }
431
432    /// Applies the method call node `function` to the given `property` by using
433    /// this symbol table.
434    pub fn build_method(
435        &self,
436        language: &L,
437        diagnostics: &mut TemplateDiagnostics,
438        build_ctx: &BuildContext<L::Property>,
439        property: OperationTemplatePropertyKind<'a>,
440        function: &FunctionCallNode,
441    ) -> TemplateParseResult<L::Property> {
442        let type_name = property.type_name();
443        match property {
444            OperationTemplatePropertyKind::Operation(property) => {
445                let table = &self.operation_methods;
446                let build = template_parser::lookup_method(type_name, table, function)?;
447                build(language, diagnostics, build_ctx, property, function)
448            }
449            OperationTemplatePropertyKind::OperationOpt(property) => {
450                let type_name = "Operation";
451                let table = &self.operation_methods;
452                let build = template_parser::lookup_method(type_name, table, function)?;
453                let inner_property = property.try_unwrap(type_name).into_dyn();
454                build(language, diagnostics, build_ctx, inner_property, function)
455            }
456            OperationTemplatePropertyKind::OperationList(property) => {
457                let table = &self.operation_list_methods;
458                let build = template_parser::lookup_method(type_name, table, function)?;
459                build(language, diagnostics, build_ctx, property, function)
460            }
461            OperationTemplatePropertyKind::OperationId(property) => {
462                let table = &self.operation_id_methods;
463                let build = template_parser::lookup_method(type_name, table, function)?;
464                build(language, diagnostics, build_ctx, property, function)
465            }
466        }
467    }
468}
469
470/// Symbol table of methods available in [`OperationTemplateLanguage`].
471pub struct OperationTemplateLanguageBuildFnTable {
472    pub core: CoreTemplateBuildFnTable<'static, OperationTemplateLanguage>,
473    pub operation: OperationTemplateBuildFnTable<'static, OperationTemplateLanguage>,
474}
475
476impl OperationTemplateLanguageBuildFnTable {
477    pub fn empty() -> Self {
478        Self {
479            core: CoreTemplateBuildFnTable::empty(),
480            operation: OperationTemplateBuildFnTable::empty(),
481        }
482    }
483
484    fn merge(&mut self, other: Self) {
485        let Self { core, operation } = other;
486
487        self.core.merge(core);
488        self.operation.merge(operation);
489    }
490
491    /// Creates new symbol table containing the builtin methods.
492    fn builtin() -> Self {
493        Self {
494            core: CoreTemplateBuildFnTable::builtin(),
495            operation: OperationTemplateBuildFnTable::builtin(),
496        }
497    }
498}
499
500fn builtin_operation_methods<'a, L>() -> TemplateBuildMethodFnMap<'a, L, Operation>
501where
502    L: TemplateLanguage<'a> + OperationTemplateEnvironment + ?Sized,
503    L::Property: OperationTemplatePropertyVar<'a>,
504{
505    // Not using maplit::hashmap!{} or custom declarative macro here because
506    // code completion inside macro is quite restricted.
507    let mut map = TemplateBuildMethodFnMap::<L, Operation>::new();
508    map.insert(
509        "current_operation",
510        |language, _diagnostics, _build_ctx, self_property, function| {
511            function.expect_no_arguments()?;
512            let current_op_id = language.current_op_id().cloned();
513            let out_property = self_property.map(move |op| Some(op.id()) == current_op_id.as_ref());
514            Ok(out_property.into_dyn_wrapped())
515        },
516    );
517    map.insert(
518        "description",
519        |_language, _diagnostics, _build_ctx, self_property, function| {
520            function.expect_no_arguments()?;
521            let out_property = self_property.map(|op| op.metadata().description.clone());
522            Ok(out_property.into_dyn_wrapped())
523        },
524    );
525    map.insert(
526        "id",
527        |_language, _diagnostics, _build_ctx, self_property, function| {
528            function.expect_no_arguments()?;
529            let out_property = self_property.map(|op| op.id().clone());
530            Ok(out_property.into_dyn_wrapped())
531        },
532    );
533    map.insert(
534        "attributes",
535        |_language, _diagnostics, _build_ctx, self_property, function| {
536            function.expect_no_arguments()?;
537            let out_property = self_property.map(|op| {
538                // TODO: introduce map type
539                op.metadata()
540                    .attributes
541                    .iter()
542                    .map(|(key, value)| format!("{key}: {value}"))
543                    .join("\n")
544            });
545            Ok(out_property.into_dyn_wrapped())
546        },
547    );
548    // TODO: Remove in jj 0.47+
549    map.insert(
550        "tags",
551        |_language, diagnostics, _build_ctx, self_property, function| {
552            diagnostics.add_warning(TemplateParseError::expression(
553                "operation.tags() is deprecated; use .attributes() instead",
554                function.name_span,
555            ));
556            function.expect_no_arguments()?;
557            let out_property = self_property.map(|op| {
558                // TODO: introduce map type
559                op.metadata()
560                    .attributes
561                    .iter()
562                    .map(|(key, value)| format!("{key}: {value}"))
563                    .join("\n")
564            });
565            Ok(out_property.into_dyn_wrapped())
566        },
567    );
568    map.insert(
569        "snapshot",
570        |_language, _diagnostics, _build_ctx, self_property, function| {
571            function.expect_no_arguments()?;
572            let out_property = self_property.map(|op| op.metadata().is_snapshot);
573            Ok(out_property.into_dyn_wrapped())
574        },
575    );
576    map.insert(
577        "workspace_name",
578        |_language, _diagnostics, _build_ctx, self_property, function| {
579            function.expect_no_arguments()?;
580            let out_property = self_property.map(|op| {
581                op.metadata()
582                    .workspace_name
583                    .as_ref()
584                    .map(|name| format!("{}@", name.as_symbol()))
585                    .unwrap_or_default()
586            });
587            Ok(out_property.into_dyn_wrapped())
588        },
589    );
590    map.insert(
591        "time",
592        |_language, _diagnostics, _build_ctx, self_property, function| {
593            function.expect_no_arguments()?;
594            let out_property = self_property.map(|op| op.metadata().time.clone());
595            Ok(out_property.into_dyn_wrapped())
596        },
597    );
598    map.insert(
599        "user",
600        |_language, _diagnostics, _build_ctx, self_property, function| {
601            function.expect_no_arguments()?;
602            let out_property = self_property.map(|op| {
603                // TODO: introduce dedicated type and provide accessors?
604                format!("{}@{}", op.metadata().username, op.metadata().hostname)
605            });
606            Ok(out_property.into_dyn_wrapped())
607        },
608    );
609    map.insert(
610        "root",
611        |language, _diagnostics, _build_ctx, self_property, function| {
612            function.expect_no_arguments()?;
613            let op_store = language.repo_loader().op_store();
614            let root_op_id = op_store.root_operation_id().clone();
615            let out_property = self_property.map(move |op| op.id() == &root_op_id);
616            Ok(out_property.into_dyn_wrapped())
617        },
618    );
619    map.insert(
620        "parents",
621        |_language, _diagnostics, _build_ctx, self_property, function| {
622            function.expect_no_arguments()?;
623            let out_property = self_property.and_then(|op| {
624                let ops = op.parents().block_on()?;
625                Ok(ops)
626            });
627            Ok(out_property.into_dyn_wrapped())
628        },
629    );
630    map
631}
632
633impl Template for OperationId {
634    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {
635        write!(formatter, "{}", self.hex())
636    }
637}
638
639fn builtin_operation_id_methods<'a, L>() -> TemplateBuildMethodFnMap<'a, L, OperationId>
640where
641    L: TemplateLanguage<'a> + OperationTemplateEnvironment + ?Sized,
642    L::Property: OperationTemplatePropertyVar<'a>,
643{
644    // Not using maplit::hashmap!{} or custom declarative macro here because
645    // code completion inside macro is quite restricted.
646    let mut map = TemplateBuildMethodFnMap::<L, OperationId>::new();
647    map.insert(
648        "short",
649        |language, diagnostics, build_ctx, self_property, function| {
650            let ([], [len_node]) = function.expect_arguments()?;
651            let len_property = len_node
652                .map(|node| {
653                    template_builder::expect_usize_expression(
654                        language,
655                        diagnostics,
656                        build_ctx,
657                        node,
658                    )
659                })
660                .transpose()?;
661            let out_property = (self_property, len_property).map(|(id, len)| {
662                let mut hex = id.hex();
663                hex.truncate(len.unwrap_or(12));
664                hex
665            });
666            Ok(out_property.into_dyn_wrapped())
667        },
668    );
669    map
670}