Merge branch 'es/worktree-cleanup'
[git] / git-gui / lib / chord.tcl
1 # Simple Chord for Tcl
2 #
3 # A "chord" is a method with more than one entrypoint and only one body, such
4 # that the body runs only once all the entrypoints have been called by
5 # different asynchronous tasks. In this implementation, the chord is defined
6 # dynamically for each invocation. A SimpleChord object is created, supplying
7 # body script to be run when the chord is completed, and then one or more notes
8 # are added to the chord. Each note can be called like a proc, and returns
9 # immediately if the chord isn't yet complete. When the last remaining note is
10 # called, the body runs before the note returns.
11 #
12 # The SimpleChord class has a constructor that takes the body script, and a
13 # method add_note that returns a note object. Since the body script does not
14 # run in the context of the procedure that defined it, a mechanism is provided
15 # for injecting variables into the chord for use by the body script. The
16 # activation of a note is idempotent; multiple calls have the same effect as
17 # a simple call.
18 #
19 # If you are invoking asynchronous operations with chord notes as completion
20 # callbacks, and there is a possibility that earlier operations could complete
21 # before later ones are started, it is a good practice to create a "common"
22 # note on the chord that prevents it from being complete until you're certain
23 # you've added all the notes you need.
24 #
25 # Example:
26 #
27 #   # Turn off the UI while running a couple of async operations.
28 #   lock_ui
29 #
30 #   set chord [SimpleChord new {
31 #     unlock_ui
32 #     # Note: $notice here is not referenced in the calling scope
33 #     if {$notice} { info_popup $notice }
34 #   }
35 #
36 #   # Configure a note to keep the chord from completing until
37 #   # all operations have been initiated.
38 #   set common_note [$chord add_note]
39 #
40 #   # Pass notes as 'after' callbacks to other operations
41 #   async_operation $args [$chord add_note]
42 #   other_async_operation $args [$chord add_note]
43 #
44 #   # Communicate with the chord body
45 #   if {$condition} {
46 #     # This sets $notice in the same context that the chord body runs in.
47 #     $chord eval { set notice "Something interesting" }
48 #   }
49 #
50 #   # Activate the common note, making the chord eligible to complete
51 #   $common_note
52 #
53 # At this point, the chord will complete at some unknown point in the future.
54 # The common note might have been the first note activated, or the async
55 # operations might have completed synchronously and the common note is the
56 # last one, completing the chord before this code finishes, or anything in
57 # between. The purpose of the chord is to not have to worry about the order.
58
59 # SimpleChord class:
60 #   Represents a procedure that conceptually has multiple entrypoints that must
61 #   all be called before the procedure executes. Each entrypoint is called a
62 #   "note". The chord is only "completed" when all the notes are "activated".
63 oo::class create SimpleChord {
64         variable notes body is_completed
65
66         # Constructor:
67         #   set chord [SimpleChord new {body}]
68         #     Creates a new chord object with the specified body script. The
69         #     body script is evaluated at most once, when a note is activated
70         #     and the chord has no other non-activated notes.
71         constructor {body} {
72                 set notes [list]
73                 my eval [list set body $body]
74                 set is_completed 0
75         }
76
77         # Method:
78         #   $chord eval {script}
79         #     Runs the specified script in the same context (namespace) in which
80         #     the chord body will be evaluated. This can be used to set variable
81         #     values for the chord body to use.
82         method eval {script} {
83                 namespace eval [self] $script
84         }
85
86         # Method:
87         #   set note [$chord add_note]
88         #     Adds a new note to the chord, an instance of ChordNote. Raises an
89         #     error if the chord is already completed, otherwise the chord is
90         #     updated so that the new note must also be activated before the
91         #     body is evaluated.
92         method add_note {} {
93                 if {$is_completed} { error "Cannot add a note to a completed chord" }
94
95                 set note [ChordNote new [self]]
96
97                 lappend notes $note
98
99                 return $note
100         }
101
102         # This method is for internal use only and is intentionally undocumented.
103         method notify_note_activation {} {
104                 if {!$is_completed} {
105                         foreach note $notes {
106                                 if {![$note is_activated]} { return }
107                         }
108
109                         set is_completed 1
110
111                         namespace eval [self] $body
112                         namespace delete [self]
113                 }
114         }
115 }
116
117 # ChordNote class:
118 #   Represents a note within a chord, providing a way to activate it. When the
119 #   final note of the chord is activated (this can be any note in the chord,
120 #   with all other notes already previously activated in any order), the chord's
121 #   body is evaluated.
122 oo::class create ChordNote {
123         variable chord is_activated
124
125         # Constructor:
126         #   Instances of ChordNote are created internally by calling add_note on
127         #   SimpleChord objects.
128         constructor {chord} {
129                 my eval set chord $chord
130                 set is_activated 0
131         }
132
133         # Method:
134         #   [$note is_activated]
135         #     Returns true if this note has already been activated.
136         method is_activated {} {
137                 return $is_activated
138         }
139
140         # Method:
141         #   $note
142         #     Activates the note, if it has not already been activated, and
143         #     completes the chord if there are no other notes awaiting
144         #     activation. Subsequent calls will have no further effect.
145         #
146         # NB: In TclOO, if an object is invoked like a method without supplying
147         #     any method name, then this internal method `unknown` is what
148         #     actually runs (with no parameters). It is used in the ChordNote
149         #     class for the purpose of allowing the note object to be called as
150         #     a function (see example above). (The `unknown` method can also be
151         #     used to support dynamic dispatch, but must take parameters to
152         #     identify the "unknown" method to be invoked. In this form, this
153         #     proc serves only to make instances behave directly like methods.)
154         method unknown {} {
155                 if {!$is_activated} {
156                         set is_activated 1
157                         $chord notify_note_activation
158                 }
159         }
160 }