summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main--chapters.vtt49
-rw-r--r--2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.vtt972
-rw-r--r--2023/info/parallel-after.md317
-rw-r--r--2023/info/parallel-before.md20
4 files changed, 1357 insertions, 1 deletions
diff --git a/2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main--chapters.vtt b/2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main--chapters.vtt
new file mode 100644
index 00000000..359a1228
--- /dev/null
+++ b/2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main--chapters.vtt
@@ -0,0 +1,49 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:23.340
+Introduction
+
+00:00:23.440 --> 00:01:12.260
+Problem: Goal
+
+00:01:12.360 --> 00:01:34.100
+Problem: Naive Multi-pass
+
+00:01:34.200 --> 00:01:57.620
+Problem: Clever Multi-pass
+
+00:01:57.720 --> 00:03:03.340
+Problem: Terminology
+
+00:03:04.440 --> 00:03:54.820
+Problem: Scaling Multi-pass
+
+00:03:55.920 --> 00:04:17.140
+Solution: Single-pass
+
+00:04:18.240 --> 00:06:28.180
+Solution: Existing
+
+00:06:29.080 --> 00:06:54.140
+Solution: query-replace-parallel
+
+00:06:55.240 --> 00:07:51.020
+Demonstration: Swap
+
+00:07:53.970 --> 00:08:46.100
+Demonstration: LaTeX
+
+00:08:48.700 --> 00:09:31.220
+Demonstration: Regex
+
+00:09:36.320 --> 00:10:52.340
+Demonstration: Order
+
+00:10:54.440 --> 00:12:26.620
+Demonstration: Fun
+
+00:12:29.120 --> 00:14:17.140
+Implementation
+
+00:14:18.740 --> 00:14:45.560
+End
diff --git a/2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.vtt b/2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.vtt
new file mode 100644
index 00000000..ba813a81
--- /dev/null
+++ b/2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.vtt
@@ -0,0 +1,972 @@
+WEBVTT captioned by Lovro
+
+NOTE Introduction
+
+00:00:00.000 --> 00:00:01.540
+Hi everyone!
+
+00:00:01.640 --> 00:00:04.540
+Welcome to our talk on Parallel Text Replacement.
+
+00:00:04.640 --> 00:00:06.940
+My name is Lovro, and I'll be telling you about an
+
+00:00:07.040 --> 00:00:09.260
+interesting problem that my friend Valentino and I
+
+00:00:09.360 --> 00:00:11.660
+set out to solve one afternoon.
+
+00:00:11.760 --> 00:00:13.580
+We will describe the problem, take a look at some
+
+00:00:13.680 --> 00:00:16.780
+of the existing work and then present our solution.
+
+00:00:16.880 --> 00:00:18.980
+Afterwards, we will show some demos and conclude
+
+00:00:19.080 --> 00:00:21.420
+with a quick overview of the implementation.
+
+00:00:21.520 --> 00:00:23.340
+Let's get straight into it!
+
+NOTE Problem: Goal
+
+00:00:23.440 --> 00:00:25.700
+Here is a problem that most of us have dealt with
+
+00:00:25.800 --> 00:00:26.940
+at some point.
+
+00:00:27.040 --> 00:00:29.780
+Assume we have a piece of code such as the following.
+
+00:00:29.880 --> 00:00:32.420
+We use a code example here, but in general what we're
+
+00:00:32.520 --> 00:00:35.500
+about to discuss can be applied to any piece of text.
+
+00:00:35.600 --> 00:00:37.540
+After a bit of thinking, we decide that the names of
+
+00:00:37.640 --> 00:00:39.860
+the two variables, "foo" and "bar", should actually be
+
+00:00:39.960 --> 00:00:40.780
+swapped.
+
+00:00:40.880 --> 00:00:43.460
+That is, "foo" should be replaced with "bar", and "bar"
+
+00:00:43.560 --> 00:00:44.940
+should be replaced with "foo".
+
+00:00:45.040 --> 00:00:48.980
+The question is: what is a good way to achieve this?
+
+00:00:49.080 --> 00:00:51.660
+We could perform the edits manually if the code is
+
+00:00:51.760 --> 00:00:53.780
+small enough, and we might even be done reasonably
+
+00:00:53.880 --> 00:00:54.620
+quickly.
+
+00:00:54.720 --> 00:00:56.620
+However, consider two things.
+
+00:00:56.720 --> 00:00:58.860
+Imagine the usual case where there's just too much
+
+00:00:58.960 --> 00:01:00.660
+code to edit by hand.
+
+00:01:00.760 --> 00:01:03.580
+We have no other option than to automate the task.
+
+00:01:03.680 --> 00:01:06.020
+More importantly though, we have a whole programmable
+
+00:01:06.120 --> 00:01:08.180
+text editor right at our fingertips.
+
+00:01:08.280 --> 00:01:10.180
+We should object to doing things that the computer
+
+00:01:10.280 --> 00:01:12.260
+can do for us.
+
+NOTE Problem: Naive Multi-pass
+
+00:01:12.360 --> 00:01:15.460
+So, one way to automate it is by using our old friend
+
+00:01:15.560 --> 00:01:18.940
+query-replace (M-%) multiple times in a sequence.
+
+00:01:19.040 --> 00:01:22.140
+We first do a pass where we replace "foo" with "bar",
+
+00:01:22.240 --> 00:01:25.540
+then we do another pass where we replace "bar" with "foo".
+
+00:01:25.640 --> 00:01:26.860
+But that's clearly not right.
+
+00:01:26.960 --> 00:01:29.060
+We all know that this naive multi-pass approach
+
+00:01:29.160 --> 00:01:31.460
+doesn't work because it results in interference
+
+00:01:31.560 --> 00:01:34.100
+between the two replacements.
+
+NOTE Problem: Clever Multi-pass
+
+00:01:34.200 --> 00:01:36.700
+Instead, we have to be a bit more clever.
+
+00:01:36.800 --> 00:01:39.740
+We should first replace "foo" with a temporary string,
+
+00:01:39.840 --> 00:01:42.020
+in this case "oof", that we will call a "token".
+
+00:01:42.120 --> 00:01:45.380
+To avoid interference, we must be careful to ensure
+
+00:01:45.480 --> 00:01:48.020
+that the token does not contain whatever we're about
+
+00:01:48.120 --> 00:01:49.500
+to replace next.
+
+00:01:49.600 --> 00:01:52.620
+Then we do a second pass to replace "bar" with "foo",
+
+00:01:52.720 --> 00:01:55.980
+and finally a third pass to replace the token with "bar".
+
+00:01:56.080 --> 00:01:57.620
+This gives us the result we want.
+
+NOTE Problem: Terminology
+
+00:01:57.720 --> 00:02:01.820
+Putting the implementation aside for a moment, this style
+
+00:02:01.920 --> 00:02:05.500
+of text replacement, where we replace multiple sources
+
+00:02:05.600 --> 00:02:08.940
+with their targets, without running into interference
+
+00:02:09.040 --> 00:02:11.660
+issues between replacement pairs, is what we call
+
+00:02:11.760 --> 00:02:12.740
+a "parallel replacement".
+
+00:02:12.840 --> 00:02:16.260
+This is the essence of the problem we're trying to solve.
+
+00:02:16.360 --> 00:02:18.580
+The examples with swapping that we've shown so far
+
+00:02:18.680 --> 00:02:21.220
+are really just one of the many use cases that are
+
+00:02:21.320 --> 00:02:23.740
+supported by a general parallel replacement utility.
+
+00:02:25.040 --> 00:02:28.660
+To avoid confusion, let us clarify that the word "parallel"
+
+00:02:28.760 --> 00:02:31.660
+is not in reference to hardware parallelization, but
+
+00:02:31.760 --> 00:02:34.780
+rather comes from analogy with the Lisp let operator,
+
+00:02:34.880 --> 00:02:38.060
+where the bindings of variables are performed in parallel,
+
+00:02:38.160 --> 00:02:40.100
+rather than sequentially as in let*.
+
+00:02:40.200 --> 00:02:43.580
+Parallel in this context means that none of the bindings
+
+00:02:43.680 --> 00:02:46.780
+are in scope within any of the initial value forms.
+
+00:02:46.880 --> 00:02:50.100
+In other words, just like a let's initialization form
+
+00:02:50.200 --> 00:02:53.620
+cannot refer to any of the earlier bindings, a
+
+00:02:53.720 --> 00:02:56.660
+replacement pair's source should not be able to replace
+
+00:02:56.760 --> 00:03:00.100
+the previously substituted targets of any other pair.
+
+00:03:00.200 --> 00:03:03.340
+This is what we mean by "no interference".
+
+NOTE Problem: Scaling Multi-pass
+
+00:03:04.440 --> 00:03:07.900
+However, manually invoking multiple carefully chosen
+
+00:03:08.000 --> 00:03:11.420
+query-replace commands gets old very quickly.
+
+00:03:11.520 --> 00:03:14.100
+Say we scaled up the problem and wanted to perform n
+
+00:03:14.200 --> 00:03:18.220
+swaps instead of just two, e.g. to swap, or rather,
+
+00:03:18.320 --> 00:03:22.060
+rotate, "foo" to "bar", "bar" to "baz", "baz" to "quux"
+
+00:03:22.160 --> 00:03:23.700
+and "quux" to "foo".
+
+00:03:23.800 --> 00:03:26.260
+We would first have to perform n - 1 additional
+
+00:03:26.360 --> 00:03:29.140
+replacements to introduce the necessary tokens,
+
+00:03:29.240 --> 00:03:32.140
+effectively doubling the number of steps.
+
+00:03:32.240 --> 00:03:34.700
+Even if we tried to automate this, think about what
+
+00:03:34.800 --> 00:03:37.580
+tokens the code would have to generate if we had no
+
+00:03:37.680 --> 00:03:40.420
+prior knowledge of the replacement pairs given by the
+
+00:03:40.520 --> 00:03:41.460
+user.
+
+00:03:41.560 --> 00:03:44.060
+We would have to program defensively and use long
+
+00:03:44.160 --> 00:03:47.460
+randomly-generated strings that, one, hopefully do
+
+00:03:47.560 --> 00:03:50.180
+not interfere with any of the replacement pairs,
+
+00:03:50.280 --> 00:03:53.380
+and two, might slow down the search if they're overly long.
+
+00:03:53.480 --> 00:03:54.820
+Can we do better?
+
+NOTE Solution: Single-pass
+
+00:03:55.920 --> 00:03:56.740
+Yes we can!
+
+00:03:56.840 --> 00:03:59.580
+We can actually perform just a single pass.
+
+00:03:59.680 --> 00:04:02.180
+The trick is to alternate between the replacement
+
+00:04:02.280 --> 00:04:05.900
+pairs, replacing whichever source occurs the earliest,
+
+00:04:06.000 --> 00:04:08.340
+and making sure to continue scanning after the end
+
+00:04:08.440 --> 00:04:12.180
+of the substituted target in order to avoid interference.
+
+00:04:12.280 --> 00:04:14.420
+This interleaving of replacements is not something
+
+00:04:14.520 --> 00:04:17.140
+that's easy to do by hand with query-replace.
+
+NOTE Solution: Existing
+
+00:04:18.240 --> 00:04:20.860
+Since this is Emacs we're talking about, of course
+
+00:04:20.960 --> 00:04:23.460
+there already exist solutions that implement this idea.
+
+00:04:23.560 --> 00:04:25.860
+Here are few that we could find.
+
+00:04:25.960 --> 00:04:28.700
+The EmacsWiki has a page dedicated to this problem.
+
+00:04:28.800 --> 00:04:31.460
+Stack Overflow has an old post where a couple of
+
+00:04:31.560 --> 00:04:33.860
+users provided their solutions.
+
+00:04:33.960 --> 00:04:36.820
+Mastering Emacs also gives a method along with other
+
+00:04:36.920 --> 00:04:38.900
+interesting query-replace-regexp (C-M-%) patterns.
+
+00:04:39.000 --> 00:04:42.260
+More recently, Tony Zorman made a blogpost providing
+
+00:04:42.360 --> 00:04:44.980
+a solution with an interface based on query-replace.
+
+00:04:45.080 --> 00:04:47.540
+I encourage you to take a look at these solutions if
+
+00:04:47.640 --> 00:04:48.940
+you're interested in the details.
+
+00:04:50.040 --> 00:04:52.940
+But while a step in the right direction, these solutions
+
+00:04:53.040 --> 00:04:55.340
+are not satisfactory because they all lack one or
+
+00:04:55.440 --> 00:04:56.820
+more of the following.
+
+00:04:56.920 --> 00:04:59.900
+One, they are not completely automated and require
+
+00:05:00.000 --> 00:05:02.500
+the user to come up with a relatively complicated
+
+00:05:02.600 --> 00:05:05.380
+and verbose query-replace-regexp invocation.
+
+00:05:06.080 --> 00:05:08.940
+Two, they are restricted to performing only 2-element
+
+00:05:09.040 --> 00:05:11.780
+swaps rather than general parallel replacements.
+
+00:05:12.680 --> 00:05:15.060
+Three, they don't provide any sort of interactivity
+
+00:05:15.160 --> 00:05:17.820
+during replacement and instead perform it in one shot.
+
+00:05:18.620 --> 00:05:21.300
+Four, they don't attempt to integrate with the familiar
+
+00:05:21.400 --> 00:05:24.900
+query-replace interface, which supports skipping, undo,
+
+00:05:25.000 --> 00:05:28.340
+history and more advanced features like Lisp expressions
+
+00:05:28.440 --> 00:05:29.900
+and recursive query edits.
+
+00:05:30.700 --> 00:05:33.700
+Most importantly however, five, none of them were
+
+00:05:33.800 --> 00:05:36.380
+designed with regular expressions in mind and instead
+
+00:05:36.480 --> 00:05:38.460
+only ever consider literal strings.
+
+00:05:39.560 --> 00:05:43.060
+In fact, the only one that comes close is the
+
+00:05:43.160 --> 00:05:46.420
+half-automated solution that invokes query-replace-regexp
+
+00:05:46.520 --> 00:05:48.100
+with a specially crafted replacement.
+
+00:05:48.800 --> 00:05:51.660
+As an example, here's how you would use this technique
+
+00:05:51.760 --> 00:05:54.340
+to perform a 3-element parallel regex replacement.
+
+00:05:54.440 --> 00:05:57.740
+It uses the backslash-comma Lisp expression feature
+
+00:05:57.840 --> 00:06:01.180
+in order to choose the appropriate target to substitute.
+
+00:06:01.280 --> 00:06:03.700
+Aside from being very clumsy and tedious to write out,
+
+00:06:03.800 --> 00:06:06.860
+this approach makes it really hard to use more complex
+
+00:06:06.960 --> 00:06:09.260
+regular expressions that make use of capture groups
+
+00:06:09.360 --> 00:06:10.600
+themselves.
+
+00:06:10.800 --> 00:06:12.200
+This was the biggest limitation that we wanted
+
+00:06:12.200 --> 00:06:15.020
+to get rid of and the main motivation for our work.
+
+00:06:15.720 --> 00:06:19.820
+So, as an alternative to the existing zoo of 80% solutions,
+
+00:06:19.920 --> 00:06:24.140
+we aim to provide a 100% solution, one that handles
+
+00:06:24.240 --> 00:06:27.020
+regexes and consolidates all of the existing ideas
+
+00:06:27.120 --> 00:06:28.180
+into a single package.
+
+NOTE Solution: query-replace-parallel
+
+00:06:29.080 --> 00:06:31.260
+We call it query-replace-parallel.
+
+00:06:31.360 --> 00:06:34.060
+The package is free and open-source and can currently
+
+00:06:34.160 --> 00:06:37.300
+be found on GitHub under hokomo/query-replace-parallel.
+
+00:06:37.400 --> 00:06:40.140
+The name is not yet finalized and we're open to any
+
+00:06:40.240 --> 00:06:41.170
+suggestions.
+
+00:06:41.503 --> 00:06:43.180
+We hope to get it published on an Elisp
+
+00:06:43.280 --> 00:06:45.780
+package archive in the near future, but for now you
+
+00:06:45.880 --> 00:06:48.300
+can just download and load the main Elisp file manually.
+
+00:06:48.900 --> 00:06:51.300
+With all of that said, let's go through a few demos
+
+00:06:51.400 --> 00:06:54.140
+to illustrate some use cases and see how to use the package.
+
+NOTE Demonstration: Swap
+
+00:06:55.240 --> 00:06:57.460
+Our first demo is a simple swap, like the one we
+
+00:06:57.560 --> 00:06:59.140
+showed at the beginning of the presentation.
+
+00:06:59.240 --> 00:07:02.060
+This chunk of text is actually one of the tests
+
+00:07:02.160 --> 00:07:03.140
+from our package's code.
+
+00:07:03.840 --> 00:07:06.420
+Assuming we have loaded the package, we can execute
+
+00:07:06.520 --> 00:07:09.580
+the query-replace-parallel command, a parallel version
+
+00:07:09.680 --> 00:07:11.220
+of the standard query-replace.
+
+00:07:11.320 --> 00:07:13.940
+This command works with literal strings and will
+
+00:07:14.040 --> 00:07:15.900
+ask for each source and target in turn.
+
+00:07:16.000 --> 00:07:21.260
+Our goal is to replace "foo" with "bar"
+
+00:07:21.360 --> 00:07:22.180
+and "bar" with "foo".
+
+00:07:24.680 --> 00:07:26.940
+After inputting our replacements, we terminate the
+
+00:07:27.040 --> 00:07:29.060
+prompt by pressing enter with empty input.
+
+00:07:29.860 --> 00:07:32.500
+At this point, everything functions the same as in
+
+00:07:32.600 --> 00:07:34.380
+a standard query-replace invocation.
+
+00:07:35.280 --> 00:07:37.300
+The echo area shows the match and the replacement
+
+00:07:37.400 --> 00:07:38.603
+we're about to make.
+
+00:07:38.703 --> 00:07:40.220
+We can perform replacements,
+
+00:07:43.920 --> 00:07:46.403
+undo them,
+
+00:07:46.503 --> 00:07:49.103
+skip them,
+
+00:07:49.203 --> 00:07:50.140
+execute them until the end,
+
+00:07:50.240 --> 00:07:51.020
+and so on.
+
+NOTE Demonstration: LaTeX
+
+00:07:53.970 --> 00:07:56.180
+The second demo shows our first regex use case.
+
+00:07:56.280 --> 00:07:58.620
+Imagine we have the following LaTeX code.
+
+00:07:58.720 --> 00:08:01.380
+We realize that we haven't been completely consistent
+
+00:08:01.480 --> 00:08:03.940
+in our use and naming of macros, so we decide to
+
+00:08:04.040 --> 00:08:04.660
+fix the problem.
+
+00:08:05.536 --> 00:08:08.300
+This time we execute query-replace-parallel-regexp
+
+00:08:08.400 --> 00:08:10.900
+because we want to work with regex instead of literal
+
+00:08:11.000 --> 00:08:11.500
+strings.
+
+00:08:12.000 --> 00:08:13.420
+We want to achieve two things.
+
+00:08:13.520 --> 00:08:16.860
+First, we want to wrap all usages of the variable n
+
+00:08:16.960 --> 00:08:17.980
+with the natvar macro.
+
+00:08:18.080 --> 00:08:20.940
+Using the backslash-less-than and blackslash-greater-than
+
+00:08:21.040 --> 00:08:23.740
+constructs allows us to only match letters n not
+
+00:08:23.840 --> 00:08:25.260
+appearing as part of a larger word.
+
+00:08:25.360 --> 00:08:29.460
+Second, we want to rename natvar to intvar because
+
+00:08:29.560 --> 00:08:32.180
+the variables a, b and c are integers and not natural
+
+00:08:32.280 --> 00:08:32.700
+numbers.
+
+00:08:33.300 --> 00:08:35.660
+We enter empty input to terminate the prompt and can
+
+00:08:35.760 --> 00:08:37.180
+now perform the replacements.
+
+00:08:42.280 --> 00:08:44.380
+There we go, the fixes are done and we didn't have
+
+00:08:44.480 --> 00:08:46.100
+to think about in which order to apply them.
+
+NOTE Demonstration: Regex
+
+00:08:48.700 --> 00:08:50.900
+We now take a look at a more complicated regex
+
+00:08:51.000 --> 00:08:53.580
+example to demonstrate that even advanced query-replace
+
+00:08:53.680 --> 00:08:54.300
+features are supported.
+
+00:08:55.100 --> 00:08:57.340
+Each "foo" and "bar" in this example is followed by
+
+00:08:57.440 --> 00:08:57.740
+a number.
+
+00:08:58.440 --> 00:09:01.280
+The goal is to not only swap "foo" and "bar", but
+
+00:09:01.380 --> 00:09:03.620
+also increase or decrease the corresponding number.
+
+00:09:03.720 --> 00:09:06.500
+We first match "foo" and capture the number that
+
+00:09:06.600 --> 00:09:07.100
+follows it.
+
+00:09:07.200 --> 00:09:09.900
+For the target, we make use of the backslash-comma
+
+00:09:10.000 --> 00:09:12.500
+Lisp expression feature in order to replace the
+
+00:09:12.600 --> 00:09:14.940
+match with "bar" followed by the number's successor.
+
+00:09:15.540 --> 00:09:17.540
+We do the same thing for "bar", except that we
+
+00:09:17.640 --> 00:09:19.140
+replace the number with its predecessor.
+
+00:09:27.040 --> 00:09:29.020
+Performing the replacements, we can see how each
+
+00:09:29.120 --> 00:09:31.220
+number is incremented or decremented appropriately.
+
+NOTE Demonstration: Order
+
+00:09:36.320 --> 00:09:38.660
+We haven't covered it explicitly so some of you may
+
+00:09:38.760 --> 00:09:41.260
+be wondering how parallel replacement deals with
+
+00:09:41.360 --> 00:09:43.740
+overlapping matches and whether the order of the
+
+00:09:43.840 --> 00:09:45.380
+replacement pairs is significant.
+
+00:09:45.480 --> 00:09:47.860
+This demo will clarify the exact behavior.
+
+00:09:48.960 --> 00:09:51.700
+The first example has the sources "watch" and "stopwatch".
+
+00:09:57.500 --> 00:10:00.500
+Conceptually, the matches overlap, but the rule is
+
+00:10:00.600 --> 00:10:02.900
+that matches are always processed earliest first,
+
+00:10:03.000 --> 00:10:05.940
+regardless of their length or the ordering of the pairs.
+
+00:10:06.040 --> 00:10:08.980
+Therefore it is "stopwatch" that gets replaced,
+
+00:10:09.080 --> 00:10:10.940
+and not its substring "watch".
+
+00:10:16.040 --> 00:10:19.540
+The second example uses the sources "watch" and "watchword".
+
+00:10:19.640 --> 00:10:22.540
+Both of the matches now conceptually start at the same
+
+00:10:22.640 --> 00:10:23.020
+position.
+
+00:10:23.720 --> 00:10:26.300
+In situations like these the order of the pairs does
+
+00:10:26.400 --> 00:10:29.460
+matter, and ties are broken by prefering the pair that
+
+00:10:29.560 --> 00:10:32.180
+was entered first, which is behavior that is inherited
+
+00:10:32.280 --> 00:10:33.460
+from the Elisp regex engine.
+
+00:10:34.460 --> 00:10:37.380
+So, the substring "watch" in "watchword" is what gets
+
+00:10:37.480 --> 00:10:38.460
+replaced in this case.
+
+00:10:39.460 --> 00:10:41.740
+Situations where the order of the pairs is significant
+
+00:10:41.840 --> 00:10:44.740
+are not very common however, so the user generally
+
+00:10:44.840 --> 00:10:46.660
+doesn't have to worry about this edge case.
+
+00:10:46.760 --> 00:10:49.860
+The order only matters when two or more sources
+
+00:10:49.960 --> 00:10:52.340
+share the same prefix, as in this example.
+
+NOTE Demonstration: Fun
+
+00:10:54.440 --> 00:10:56.860
+The final demo tests the limits of the package and
+
+00:10:56.960 --> 00:10:59.660
+shows that it fully integrates with query-replace.
+
+00:10:59.760 --> 00:11:02.940
+It is really just for fun and can even serve as a
+
+00:11:03.040 --> 00:11:04.140
+small Emacs brainteaser.
+
+00:11:04.240 --> 00:11:05.460
+See if you can keep up!
+
+00:11:06.360 --> 00:11:09.060
+We open a directory and enter Writable Dired mode
+
+00:11:09.160 --> 00:11:11.780
+in order to rename the directories "foo" and "bar".
+
+00:11:11.880 --> 00:11:14.660
+Instead of doing it quickly by hand, we decide to
+
+00:11:14.760 --> 00:11:17.260
+show off and use query-replace-parallel-regexp.
+
+00:11:17.360 --> 00:11:19.900
+We enter our pairs and make use of the
+
+00:11:20.000 --> 00:11:22.380
+backslash-question-mark query edit feature.
+
+00:11:25.080 --> 00:11:27.820
+Now whenever we perform a replacement, the query
+
+00:11:27.920 --> 00:11:30.740
+edit makes Emacs stop and prompt us for additional
+
+00:11:30.840 --> 00:11:32.180
+input to use as the target.
+
+00:11:36.680 --> 00:11:39.140
+We confirm the renames and now enter the "bar-lib"
+
+00:11:39.240 --> 00:11:41.900
+directory in order to perform the same kind of
+
+00:11:42.000 --> 00:11:43.900
+replacement on "baz" and "quux".
+
+00:11:44.500 --> 00:11:47.820
+Rather than save time, we decide to be extra lazy
+
+00:11:47.920 --> 00:11:48.820
+and take the long route.
+
+00:11:48.920 --> 00:11:52.220
+We recall the first pair and initiate a recursive
+
+00:11:52.320 --> 00:11:54.460
+invocation of query-replace-parallel-regexp.
+
+00:11:54.560 --> 00:11:57.020
+We are now replacing the replacement.
+
+00:12:01.020 --> 00:12:04.540
+We apply our fixes and then do the same thing again
+
+00:12:04.640 --> 00:12:05.870
+with the second pair.
+
+00:12:05.970 --> 00:12:07.500
+Recall and recurse.
+
+00:12:16.300 --> 00:12:19.860
+We confirm the prompt and finally rename our directories.
+
+00:12:25.360 --> 00:12:26.620
+Wow, that really paid off.
+
+NOTE Implementation
+
+00:12:29.120 --> 00:12:31.380
+Before we finish, a few quick words about the
+
+00:12:31.480 --> 00:12:32.900
+implementation for the curious.
+
+00:12:33.300 --> 00:12:36.380
+Both query-replace-parallel and query-replace-parallel-regexp
+
+00:12:36.480 --> 00:12:39.140
+delegate to the complex perform-replace function
+
+00:12:39.240 --> 00:12:41.780
+which is the workhorse of query-replace's interactive
+
+00:12:41.880 --> 00:12:42.420
+mechanism.
+
+00:12:43.120 --> 00:12:45.420
+The way we achieve multiple interleaved replacements
+
+00:12:45.520 --> 00:12:49.020
+is by providing perform-replace with a big "matcher regex"
+
+00:12:49.120 --> 00:12:50.380
+and a special replacement function.
+
+00:12:50.480 --> 00:12:54.300
+Essentially, a complex parallel replacement like this
+
+00:12:54.400 --> 00:12:57.420
+is transformed into a standard replacement like this.
+
+00:12:57.520 --> 00:13:00.100
+This is similar to the trick shown earlier in the
+
+00:13:00.200 --> 00:13:00.780
+presentation.
+
+00:13:00.880 --> 00:13:03.820
+Each source is put in its own capture group to allow
+
+00:13:03.920 --> 00:13:06.340
+the replacement function to determine which one matched
+
+00:13:06.440 --> 00:13:08.380
+and return the appropriate target.
+
+00:13:08.980 --> 00:13:11.580
+However, we now take care to support arbitrary
+
+00:13:11.680 --> 00:13:13.380
+regular expressions as sources.
+
+00:13:13.480 --> 00:13:16.980
+We achieve this by converting each source regex into
+
+00:13:17.080 --> 00:13:19.820
+an equivalent one for which we can guarantee that its
+
+00:13:19.920 --> 00:13:22.820
+capture groups will not clash with our matcher regex.
+
+00:13:22.920 --> 00:13:25.900
+Information about this conversion is stored, and
+
+00:13:26.000 --> 00:13:28.220
+once the replacement function is called it has
+
+00:13:28.320 --> 00:13:30.260
+enough data to apply the replacement from the
+
+00:13:30.360 --> 00:13:32.020
+viewpoint of the original regex.
+
+00:13:32.720 --> 00:13:34.900
+The regex transformation is reliable because it
+
+00:13:35.000 --> 00:13:38.420
+uses the rx library, allowing us to treat regexes
+
+00:13:38.520 --> 00:13:41.940
+as s-expressions and avoid any nasty manual parsing.
+
+00:13:42.640 --> 00:13:46.540
+In fact, rx itself is based on one of Olin Shivers'
+
+00:13:46.640 --> 00:13:48.336
+100% solutions:
+
+00:13:48.436 --> 00:13:51.220
+SRE, or the S-expression regex notation.
+
+00:13:51.320 --> 00:13:54.340
+We all stand on the shoulders of many giants, so
+
+00:13:54.440 --> 00:13:56.500
+let's strive to design good solutions that we can
+
+00:13:56.600 --> 00:13:59.140
+all benefit from, many years into the future!
+
+00:13:59.240 --> 00:14:02.900
+Finally, because query-replace's core is not completely
+
+00:14:03.000 --> 00:14:06.060
+customizable, we did have to sprinkle in some advice
+
+00:14:06.160 --> 00:14:07.500
+to get certain things working.
+
+00:14:07.600 --> 00:14:11.060
+This concerns only minor cosmetic fixes and not the
+
+00:14:11.160 --> 00:14:13.940
+core replacement functionality, but we have nontheless
+
+00:14:14.040 --> 00:14:16.580
+tried to do it in the simplest and least intrusive way
+
+00:14:16.680 --> 00:14:17.140
+possible.
+
+NOTE End
+
+00:14:18.740 --> 00:14:21.580
+In conclusion, go download and play with the package.
+
+00:14:21.680 --> 00:14:24.460
+Even if you're not performing overlapping replacements,
+
+00:14:24.560 --> 00:14:26.780
+you can still use query-replace-parallel for the
+
+00:14:26.880 --> 00:14:29.620
+peace of mind knowing that things won't go wrong if
+
+00:14:29.720 --> 00:14:31.860
+you perform more than one replacement at a time.
+
+00:14:32.460 --> 00:14:34.540
+Feel free to let us know about any interesting or
+
+00:14:34.640 --> 00:14:37.460
+crazy use cases you might come up with, as well as
+
+00:14:37.560 --> 00:14:40.540
+improvements or bugs that make it only a 99% solution.
+
+00:14:40.640 --> 00:14:45.560
+Thanks for listening and have a great EmacsConf!
diff --git a/2023/info/parallel-after.md b/2023/info/parallel-after.md
index 0eae3eb3..742148aa 100644
--- a/2023/info/parallel-after.md
+++ b/2023/info/parallel-after.md
@@ -1,6 +1,323 @@
<!-- Automatically generated by emacsconf-publish-after-page -->
+<a name="parallel-mainVideo-transcript"></a>
+# Transcript
+
+[[!template new="1" text="""Hi everyone!""" start="00:00:00.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Welcome to our talk on Parallel Text Replacement.""" start="00:00:01.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""My name is Lovro, and I'll be telling you about an""" start="00:00:04.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""interesting problem that my friend Valentino and I""" start="00:00:07.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""set out to solve one afternoon.""" start="00:00:09.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We will describe the problem, take a look at some""" start="00:00:11.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""of the existing work and then present our solution.""" start="00:00:13.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Afterwards, we will show some demos and conclude""" start="00:00:16.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""with a quick overview of the implementation.""" start="00:00:19.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Let's get straight into it!""" start="00:00:21.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""Here is a problem that most of us have dealt with""" start="00:00:23.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""at some point.""" start="00:00:25.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Assume we have a piece of code such as the following.""" start="00:00:27.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We use a code example here, but in general what we're""" start="00:00:29.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""about to discuss can be applied to any piece of text.""" start="00:00:32.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""After a bit of thinking, we decide that the names of""" start="00:00:35.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""the two variables, &quot;foo&quot; and &quot;bar&quot;, should actually be""" start="00:00:37.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""swapped.""" start="00:00:39.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""That is, &quot;foo&quot; should be replaced with &quot;bar&quot;, and &quot;bar&quot;""" start="00:00:40.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""should be replaced with &quot;foo&quot;.""" start="00:00:43.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The question is: what is a good way to achieve this?""" start="00:00:45.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We could perform the edits manually if the code is""" start="00:00:49.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""small enough, and we might even be done reasonably""" start="00:00:51.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""quickly.""" start="00:00:53.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""However, consider two things.""" start="00:00:54.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Imagine the usual case where there's just too much""" start="00:00:56.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""code to edit by hand.""" start="00:00:58.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We have no other option than to automate the task.""" start="00:01:00.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""More importantly though, we have a whole programmable""" start="00:01:03.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""text editor right at our fingertips.""" start="00:01:06.120" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We should object to doing things that the computer""" start="00:01:08.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""can do for us.""" start="00:01:10.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""So, one way to automate it is by using our old friend""" start="00:01:12.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""query-replace (M-%) multiple times in a sequence.""" start="00:01:15.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We first do a pass where we replace &quot;foo&quot; with &quot;bar&quot;,""" start="00:01:19.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""then we do another pass where we replace &quot;bar&quot; with &quot;foo&quot;.""" start="00:01:22.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""But that's clearly not right.""" start="00:01:25.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We all know that this naive multi-pass approach""" start="00:01:26.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""doesn't work because it results in interference""" start="00:01:29.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""between the two replacements.""" start="00:01:31.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""Instead, we have to be a bit more clever.""" start="00:01:34.200" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We should first replace &quot;foo&quot; with a temporary string,""" start="00:01:36.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""in this case &quot;oof&quot;, that we will call a &quot;token&quot;.""" start="00:01:39.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""To avoid interference, we must be careful to ensure""" start="00:01:42.120" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""that the token does not contain whatever we're about""" start="00:01:45.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""to replace next.""" start="00:01:48.120" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Then we do a second pass to replace &quot;bar&quot; with &quot;foo&quot;,""" start="00:01:49.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and finally a third pass to replace the token with &quot;bar&quot;.""" start="00:01:52.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This gives us the result we want.""" start="00:01:56.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""Putting the implementation aside for a moment, this style""" start="00:01:57.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""of text replacement, where we replace multiple sources""" start="00:02:01.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""with their targets, without running into interference""" start="00:02:05.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""issues between replacement pairs, is what we call""" start="00:02:09.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""a &quot;parallel replacement&quot;.""" start="00:02:11.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This is the essence of the problem we're trying to solve.""" start="00:02:12.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The examples with swapping that we've shown so far""" start="00:02:16.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""are really just one of the many use cases that are""" start="00:02:18.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""supported by a general parallel replacement utility.""" start="00:02:21.320" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""To avoid confusion, let us clarify that the word &quot;parallel&quot;""" start="00:02:25.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""is not in reference to hardware parallelization, but""" start="00:02:28.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""rather comes from analogy with the Lisp let operator,""" start="00:02:31.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""where the bindings of variables are performed in parallel,""" start="00:02:34.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""rather than sequentially as in let*.""" start="00:02:38.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Parallel in this context means that none of the bindings""" start="00:02:40.200" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""are in scope within any of the initial value forms.""" start="00:02:43.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""In other words, just like a let's initialization form""" start="00:02:46.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""cannot refer to any of the earlier bindings, a""" start="00:02:50.200" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""replacement pair's source should not be able to replace""" start="00:02:53.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""the previously substituted targets of any other pair.""" start="00:02:56.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This is what we mean by &quot;no interference&quot;.""" start="00:03:00.200" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""However, manually invoking multiple carefully chosen""" start="00:03:04.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""query-replace commands gets old very quickly.""" start="00:03:08.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Say we scaled up the problem and wanted to perform n""" start="00:03:11.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""swaps instead of just two, e.g. to swap, or rather,""" start="00:03:14.200" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""rotate, &quot;foo&quot; to &quot;bar&quot;, &quot;bar&quot; to &quot;baz&quot;, &quot;baz&quot; to &quot;quux&quot;""" start="00:03:18.320" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and &quot;quux&quot; to &quot;foo&quot;.""" start="00:03:22.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We would first have to perform n - 1 additional""" start="00:03:23.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""replacements to introduce the necessary tokens,""" start="00:03:26.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""effectively doubling the number of steps.""" start="00:03:29.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Even if we tried to automate this, think about what""" start="00:03:32.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""tokens the code would have to generate if we had no""" start="00:03:34.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""prior knowledge of the replacement pairs given by the""" start="00:03:37.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""user.""" start="00:03:40.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We would have to program defensively and use long""" start="00:03:41.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""randomly-generated strings that, one, hopefully do""" start="00:03:44.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""not interfere with any of the replacement pairs,""" start="00:03:47.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and two, might slow down the search if they're overly long.""" start="00:03:50.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Can we do better?""" start="00:03:53.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""Yes we can!""" start="00:03:55.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We can actually perform just a single pass.""" start="00:03:56.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The trick is to alternate between the replacement""" start="00:03:59.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""pairs, replacing whichever source occurs the earliest,""" start="00:04:02.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and making sure to continue scanning after the end""" start="00:04:06.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""of the substituted target in order to avoid interference.""" start="00:04:08.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This interleaving of replacements is not something""" start="00:04:12.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""that's easy to do by hand with query-replace.""" start="00:04:14.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""Since this is Emacs we're talking about, of course""" start="00:04:18.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""there already exist solutions that implement this idea.""" start="00:04:20.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Here are few that we could find.""" start="00:04:23.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The EmacsWiki has a page dedicated to this problem.""" start="00:04:25.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Stack Overflow has an old post where a couple of""" start="00:04:28.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""users provided their solutions.""" start="00:04:31.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Mastering Emacs also gives a method along with other""" start="00:04:33.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""interesting query-replace-regexp (C-M-%) patterns.""" start="00:04:36.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""More recently, Tony Zorman made a blogpost providing""" start="00:04:39.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""a solution with an interface based on query-replace.""" start="00:04:42.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""I encourage you to take a look at these solutions if""" start="00:04:45.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""you're interested in the details.""" start="00:04:47.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""But while a step in the right direction, these solutions""" start="00:04:50.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""are not satisfactory because they all lack one or""" start="00:04:53.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""more of the following.""" start="00:04:55.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""One, they are not completely automated and require""" start="00:04:56.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""the user to come up with a relatively complicated""" start="00:05:00.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and verbose query-replace-regexp invocation.""" start="00:05:02.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Two, they are restricted to performing only 2-element""" start="00:05:06.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""swaps rather than general parallel replacements.""" start="00:05:09.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Three, they don't provide any sort of interactivity""" start="00:05:12.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""during replacement and instead perform it in one shot.""" start="00:05:15.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Four, they don't attempt to integrate with the familiar""" start="00:05:18.620" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""query-replace interface, which supports skipping, undo,""" start="00:05:21.400" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""history and more advanced features like Lisp expressions""" start="00:05:25.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and recursive query edits.""" start="00:05:28.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Most importantly however, five, none of them were""" start="00:05:30.700" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""designed with regular expressions in mind and instead""" start="00:05:33.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""only ever consider literal strings.""" start="00:05:36.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""In fact, the only one that comes close is the""" start="00:05:39.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""half-automated solution that invokes query-replace-regexp""" start="00:05:43.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""with a specially crafted replacement.""" start="00:05:46.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""As an example, here's how you would use this technique""" start="00:05:48.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""to perform a 3-element parallel regex replacement.""" start="00:05:51.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""It uses the backslash-comma Lisp expression feature""" start="00:05:54.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""in order to choose the appropriate target to substitute.""" start="00:05:57.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Aside from being very clumsy and tedious to write out,""" start="00:06:01.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""this approach makes it really hard to use more complex""" start="00:06:03.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""regular expressions that make use of capture groups""" start="00:06:06.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""themselves.""" start="00:06:09.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This was the biggest limitation that we wanted""" start="00:06:10.800" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""to get rid of and the main motivation for our work.""" start="00:06:12.200" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""So, as an alternative to the existing zoo of 80% solutions,""" start="00:06:15.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""we aim to provide a 100% solution, one that handles""" start="00:06:19.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""regexes and consolidates all of the existing ideas""" start="00:06:24.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""into a single package.""" start="00:06:27.120" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""We call it query-replace-parallel.""" start="00:06:29.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The package is free and open-source and can currently""" start="00:06:31.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""be found on GitHub under hokomo/query-replace-parallel.""" start="00:06:34.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The name is not yet finalized and we're open to any""" start="00:06:37.400" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""suggestions.""" start="00:06:40.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We hope to get it published on an Elisp""" start="00:06:41.503" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""package archive in the near future, but for now you""" start="00:06:43.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""can just download and load the main Elisp file manually.""" start="00:06:45.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""With all of that said, let's go through a few demos""" start="00:06:48.900" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""to illustrate some use cases and see how to use the package.""" start="00:06:51.400" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""Our first demo is a simple swap, like the one we""" start="00:06:55.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""showed at the beginning of the presentation.""" start="00:06:57.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This chunk of text is actually one of the tests""" start="00:06:59.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""from our package's code.""" start="00:07:02.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Assuming we have loaded the package, we can execute""" start="00:07:03.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""the query-replace-parallel command, a parallel version""" start="00:07:06.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""of the standard query-replace.""" start="00:07:09.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This command works with literal strings and will""" start="00:07:11.320" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""ask for each source and target in turn.""" start="00:07:14.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Our goal is to replace &quot;foo&quot; with &quot;bar&quot;""" start="00:07:16.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and &quot;bar&quot; with &quot;foo&quot;.""" start="00:07:21.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""After inputting our replacements, we terminate the""" start="00:07:24.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""prompt by pressing enter with empty input.""" start="00:07:27.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""At this point, everything functions the same as in""" start="00:07:29.860" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""a standard query-replace invocation.""" start="00:07:32.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The echo area shows the match and the replacement""" start="00:07:35.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""we're about to make.""" start="00:07:37.400" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We can perform replacements,""" start="00:07:38.703" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""undo them,""" start="00:07:43.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""skip them,""" start="00:07:46.503" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""execute them until the end,""" start="00:07:49.203" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and so on.""" start="00:07:50.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""The second demo shows our first regex use case.""" start="00:07:53.970" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Imagine we have the following LaTeX code.""" start="00:07:56.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We realize that we haven't been completely consistent""" start="00:07:58.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""in our use and naming of macros, so we decide to""" start="00:08:01.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""fix the problem.""" start="00:08:04.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This time we execute query-replace-parallel-regexp""" start="00:08:05.536" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""because we want to work with regex instead of literal""" start="00:08:08.400" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""strings.""" start="00:08:11.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We want to achieve two things.""" start="00:08:12.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""First, we want to wrap all usages of the variable n""" start="00:08:13.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""with the natvar macro.""" start="00:08:16.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Using the backslash-less-than and blackslash-greater-than""" start="00:08:18.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""constructs allows us to only match letters n not""" start="00:08:21.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""appearing as part of a larger word.""" start="00:08:23.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Second, we want to rename natvar to intvar because""" start="00:08:25.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""the variables a, b and c are integers and not natural""" start="00:08:29.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""numbers.""" start="00:08:32.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We enter empty input to terminate the prompt and can""" start="00:08:33.300" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""now perform the replacements.""" start="00:08:35.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""There we go, the fixes are done and we didn't have""" start="00:08:42.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""to think about in which order to apply them.""" start="00:08:44.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""We now take a look at a more complicated regex""" start="00:08:48.700" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""example to demonstrate that even advanced query-replace""" start="00:08:51.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""features are supported.""" start="00:08:53.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Each &quot;foo&quot; and &quot;bar&quot; in this example is followed by""" start="00:08:55.100" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""a number.""" start="00:08:57.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The goal is to not only swap &quot;foo&quot; and &quot;bar&quot;, but""" start="00:08:58.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""also increase or decrease the corresponding number.""" start="00:09:01.380" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We first match &quot;foo&quot; and capture the number that""" start="00:09:03.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""follows it.""" start="00:09:06.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""For the target, we make use of the backslash-comma""" start="00:09:07.200" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Lisp expression feature in order to replace the""" start="00:09:10.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""match with &quot;bar&quot; followed by the number's successor.""" start="00:09:12.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We do the same thing for &quot;bar&quot;, except that we""" start="00:09:15.540" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""replace the number with its predecessor.""" start="00:09:17.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Performing the replacements, we can see how each""" start="00:09:27.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""number is incremented or decremented appropriately.""" start="00:09:29.120" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""We haven't covered it explicitly so some of you may""" start="00:09:36.320" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""be wondering how parallel replacement deals with""" start="00:09:38.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""overlapping matches and whether the order of the""" start="00:09:41.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""replacement pairs is significant.""" start="00:09:43.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This demo will clarify the exact behavior.""" start="00:09:45.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The first example has the sources &quot;watch&quot; and &quot;stopwatch&quot;.""" start="00:09:48.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Conceptually, the matches overlap, but the rule is""" start="00:09:57.500" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""that matches are always processed earliest first,""" start="00:10:00.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""regardless of their length or the ordering of the pairs.""" start="00:10:03.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Therefore it is &quot;stopwatch&quot; that gets replaced,""" start="00:10:06.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and not its substring &quot;watch&quot;.""" start="00:10:09.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The second example uses the sources &quot;watch&quot; and &quot;watchword&quot;.""" start="00:10:16.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Both of the matches now conceptually start at the same""" start="00:10:19.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""position.""" start="00:10:22.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""In situations like these the order of the pairs does""" start="00:10:23.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""matter, and ties are broken by prefering the pair that""" start="00:10:26.400" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""was entered first, which is behavior that is inherited""" start="00:10:29.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""from the Elisp regex engine.""" start="00:10:32.280" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""So, the substring &quot;watch&quot; in &quot;watchword&quot; is what gets""" start="00:10:34.460" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""replaced in this case.""" start="00:10:37.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Situations where the order of the pairs is significant""" start="00:10:39.460" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""are not very common however, so the user generally""" start="00:10:41.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""doesn't have to worry about this edge case.""" start="00:10:44.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The order only matters when two or more sources""" start="00:10:46.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""share the same prefix, as in this example.""" start="00:10:49.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""The final demo tests the limits of the package and""" start="00:10:54.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""shows that it fully integrates with query-replace.""" start="00:10:56.960" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""It is really just for fun and can even serve as a""" start="00:10:59.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""small Emacs brainteaser.""" start="00:11:03.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""See if you can keep up!""" start="00:11:04.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We open a directory and enter Writable Dired mode""" start="00:11:06.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""in order to rename the directories &quot;foo&quot; and &quot;bar&quot;.""" start="00:11:09.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Instead of doing it quickly by hand, we decide to""" start="00:11:11.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""show off and use query-replace-parallel-regexp.""" start="00:11:14.760" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We enter our pairs and make use of the""" start="00:11:17.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""backslash-question-mark query edit feature.""" start="00:11:20.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Now whenever we perform a replacement, the query""" start="00:11:25.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""edit makes Emacs stop and prompt us for additional""" start="00:11:27.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""input to use as the target.""" start="00:11:30.840" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We confirm the renames and now enter the &quot;bar-lib&quot;""" start="00:11:36.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""directory in order to perform the same kind of""" start="00:11:39.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""replacement on &quot;baz&quot; and &quot;quux&quot;.""" start="00:11:42.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Rather than save time, we decide to be extra lazy""" start="00:11:44.500" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and take the long route.""" start="00:11:47.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We recall the first pair and initiate a recursive""" start="00:11:48.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""invocation of query-replace-parallel-regexp.""" start="00:11:52.320" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We are now replacing the replacement.""" start="00:11:54.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We apply our fixes and then do the same thing again""" start="00:12:01.020" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""with the second pair.""" start="00:12:04.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Recall and recurse.""" start="00:12:05.970" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We confirm the prompt and finally rename our directories.""" start="00:12:16.300" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Wow, that really paid off.""" start="00:12:25.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""Before we finish, a few quick words about the""" start="00:12:29.120" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""implementation for the curious.""" start="00:12:31.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Both query-replace-parallel and query-replace-parallel-regexp""" start="00:12:33.300" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""delegate to the complex perform-replace function""" start="00:12:36.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""which is the workhorse of query-replace's interactive""" start="00:12:39.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""mechanism.""" start="00:12:41.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The way we achieve multiple interleaved replacements""" start="00:12:43.120" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""is by providing perform-replace with a big &quot;matcher regex&quot;""" start="00:12:45.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and a special replacement function.""" start="00:12:49.120" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Essentially, a complex parallel replacement like this""" start="00:12:50.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""is transformed into a standard replacement like this.""" start="00:12:54.400" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This is similar to the trick shown earlier in the""" start="00:12:57.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""presentation.""" start="00:13:00.200" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Each source is put in its own capture group to allow""" start="00:13:00.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""the replacement function to determine which one matched""" start="00:13:03.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""and return the appropriate target.""" start="00:13:06.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""However, we now take care to support arbitrary""" start="00:13:08.980" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""regular expressions as sources.""" start="00:13:11.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We achieve this by converting each source regex into""" start="00:13:13.480" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""an equivalent one for which we can guarantee that its""" start="00:13:17.080" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""capture groups will not clash with our matcher regex.""" start="00:13:19.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Information about this conversion is stored, and""" start="00:13:22.920" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""once the replacement function is called it has""" start="00:13:26.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""enough data to apply the replacement from the""" start="00:13:28.320" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""viewpoint of the original regex.""" start="00:13:30.360" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""The regex transformation is reliable because it""" start="00:13:32.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""uses the rx library, allowing us to treat regexes""" start="00:13:35.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""as s-expressions and avoid any nasty manual parsing.""" start="00:13:38.520" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""In fact, rx itself is based on one of Olin Shivers'""" start="00:13:42.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""100% solutions:""" start="00:13:46.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""SRE, or the S-expression regex notation.""" start="00:13:48.436" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""We all stand on the shoulders of many giants, so""" start="00:13:51.320" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""let's strive to design good solutions that we can""" start="00:13:54.440" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""all benefit from, many years into the future!""" start="00:13:56.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Finally, because query-replace's core is not completely""" start="00:13:59.240" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""customizable, we did have to sprinkle in some advice""" start="00:14:03.000" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""to get certain things working.""" start="00:14:06.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""This concerns only minor cosmetic fixes and not the""" start="00:14:07.600" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""core replacement functionality, but we have nontheless""" start="00:14:11.160" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""tried to do it in the simplest and least intrusive way""" start="00:14:14.040" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""possible.""" start="00:14:16.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template new="1" text="""In conclusion, go download and play with the package.""" start="00:14:18.740" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Even if you're not performing overlapping replacements,""" start="00:14:21.680" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""you can still use query-replace-parallel for the""" start="00:14:24.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""peace of mind knowing that things won't go wrong if""" start="00:14:26.880" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""you perform more than one replacement at a time.""" start="00:14:29.720" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Feel free to let us know about any interesting or""" start="00:14:32.460" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""crazy use cases you might come up with, as well as""" start="00:14:34.640" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""improvements or bugs that make it only a 99% solution.""" start="00:14:37.560" video="mainVideo-parallel" id="subtitle"]]
+[[!template text="""Thanks for listening and have a great EmacsConf!""" start="00:14:40.640" video="mainVideo-parallel" id="subtitle"]]
+
Questions or comments? Please e-mail [hokomo@disroot.org](mailto:hokomo@disroot.org?subject=Comment%20for%20EmacsConf%202022%20parallel%3A%20Parallel%20text%20replacement)
diff --git a/2023/info/parallel-before.md b/2023/info/parallel-before.md
index e4bbe73e..0b090a8b 100644
--- a/2023/info/parallel-before.md
+++ b/2023/info/parallel-before.md
@@ -8,12 +8,30 @@ The following image shows where the talk is in the schedule for Sun 2023-12-03.
Format: 15-min talk; Q&A: BigBlueButton conference room <https://media.emacsconf.org/2023/current/bbb-parallel.html>
Etherpad: <https://pad.emacsconf.org/2023-parallel>
Discuss on IRC: [#emacsconf-gen](https://chat.emacsconf.org/?join=emacsconf,emacsconf-gen)
-Status: Processing uploaded video
+Status: Now playing on the conference livestream
<div>Times in different timezones:</div><div class="times" start="2023-12-03T15:10:00Z" end="2023-12-03T15:25:00Z"><div class="conf-time">Sunday, Dec 3 2023, ~10:10 AM - 10:25 AM EST (US/Eastern)</div><div class="others"><div>which is the same as:</div>Sunday, Dec 3 2023, ~9:10 AM - 9:25 AM CST (US/Central)<br />Sunday, Dec 3 2023, ~8:10 AM - 8:25 AM MST (US/Mountain)<br />Sunday, Dec 3 2023, ~7:10 AM - 7:25 AM PST (US/Pacific)<br />Sunday, Dec 3 2023, ~3:10 PM - 3:25 PM UTC <br />Sunday, Dec 3 2023, ~4:10 PM - 4:25 PM CET (Europe/Paris)<br />Sunday, Dec 3 2023, ~5:10 PM - 5:25 PM EET (Europe/Athens)<br />Sunday, Dec 3 2023, ~8:40 PM - 8:55 PM IST (Asia/Kolkata)<br />Sunday, Dec 3 2023, ~11:10 PM - 11:25 PM +08 (Asia/Singapore)<br />Monday, Dec 4 2023, ~12:10 AM - 12:25 AM JST (Asia/Tokyo)</div></div><div><strong><a href="/2023/watch/gen/">Find out how to watch and participate</a></strong></div>
+<div class="vid"><video controls preload="none" id="parallel-mainVideo"><source src="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--original.webm" />captions="""<track label="English" kind="captions" srclang="en" src="/2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.vtt" default />"""<track kind="chapters" label="Chapters" src="/2023/captions/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main--chapters.vtt" /><p><em>Your browser does not support the video tag. Please download the video instead.</em></p></video>[[!template id="chapters" vidid="parallel-mainVideo" data="""
+00:00.000 Introduction
+00:23.440 Problem: Goal
+01:12.360 Problem: Naive Multi-pass
+01:34.200 Problem: Clever Multi-pass
+01:57.720 Problem: Terminology
+03:04.440 Problem: Scaling Multi-pass
+03:55.920 Solution: Single-pass
+04:18.240 Solution: Existing
+06:29.080 Solution: query-replace-parallel
+06:55.240 Demonstration: Swap
+07:53.970 Demonstration: LaTeX
+08:48.700 Demonstration: Regex
+09:36.320 Demonstration: Order
+10:54.440 Demonstration: Fun
+12:29.120 Implementation
+14:18.740 End
+"""]]<div></div>Duration: 14:46 minutes<div class="files resources"><ul><li><a href="https://pad.emacsconf.org/2023-parallel">Open Etherpad</a></li><li><a href="https://media.emacsconf.org/2023/current/bbb-parallel.html">Open public Q&A</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--intro.vtt">Download --intro.vtt</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--intro.webm">Download --intro.webm</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main--chapters.vtt">Download --main--chapters.vtt</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main-vp8.webm">Download --main-vp8.webm (95MB)</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.opus">Download --main.opus</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.txt">Download --main.txt</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.vtt">Download --main.vtt</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--main.webm">Download --main.webm (29MB)</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--normalized.opus">Download --normalized.opus (15MB)</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--original.opus">Download --original.opus (13MB)</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--original.webm">Download --original.webm (94MB)</a></li><li><a href="https://media.emacsconf.org/2023/emacsconf-2023-parallel--parallel-text-replacement--lovro-valentino-picotti--reencoded.webm">Download --reencoded.webm (25MB)</a></li></ul></div></div>
# Description
<!-- End of emacsconf-publish-before-page --> \ No newline at end of file