 Actually, the first thing is how do you pronounce zai? That's kind of what I've settled on. Everybody has a different idea, but that's excellent. So yes, I'm Ray Flavina. I'm at Google. This is actually my 20% project. This is not an official Google product. And to the extent that I express opinions, those are my own and not of Google. So let me show you a little bit, like, you know, just jump in and do a little bit of a demonstration. So one of the things that I do a lot is that I open a lot of extremely large files. So this file is 380 megabytes. And it loaded pretty fast. I actually want to get that even faster by loading asynchronously. But that's not the way it works right now. It's a synchronous load. So it took about maybe a second, maybe a little bit more. And then as I scroll through that, you know, I'm getting like complete 60 frames a second, just butter smooth scrolling. That's the goal of this editor, is extreme performance. So let's talk a little bit about, you know, what is the shape of this project overall? It's Hoson GitHub. It's under an Apache 2 license. It's completely, you know, it's not just open source, but like really kind of community-based open source project. I started it a little bit more than six months ago and took it public, you know, it's kind of doing it just on my own for a couple of months, took it public in late April. The code base is in a couple of different pieces. There's almost 10,000 lines of Rust code, which is the core and then a bunch of libraries. And the libraries potentially have interest, you know, outside this project. And especially there's this rope library, which is the string representation. I'll talk about a lot more detail later. And that's almost half of the code. It's all in stable Rust, completely safe, zero uses of unsafe in the code. And I thought the earlier talk was really interesting about the use of traits. The ratio of traits is actually pretty high compared with some of the other, I do use traits extensively and I'll talk about that in a little bit more detail. There's also 1200 lines of Swift code in this project, which is the Cocoa front end. It's a Mac app, although the intent is, I wanna see more front ends. I wanna see lots of different platforms that this runs on. So it's my 20% project open to the community. There have been 27 contributors so far and I'm hoping that there will be a little step function, hopefully as a result of this talk. So the goal of this editor is I kinda open up, is performance, and how am I achieving performance? There's a bunch of different ways you do that. So the most obvious is use a fast language. Rust. And then I wanted to do it in a more modern, I really wanna use kind of the best known techniques of doing all of these text editing primitives. And if you look back 20, 30 years ago when people were kind of building these tools, multi-core parallels and doesn't really exist. You don't really try and use that to solve problems. So I haven't done a lot yet, but I want the architecture to support do as much work in parallel as possible. Where I think things get even more interesting is to use the most advanced data structures and the most advanced algorithms for manipulating text. So the data structure is ropes and as far as algorithms, the most important thing to do is do as little work as possible. So when you do an edit, you do an incremental computation of only updating just a few tiny things around that edit, get that on the screen and don't like recompute things. But I think one of the things that makes XI most unlike previous editor projects is using a synchrony as a core defining principle. There are other projects, certainly it's inspired by NeoVim, which tries to do that, but I think I've taken it a little bit farther. So the goal is to never block on slow operations. So this is a little bit of a picture of the way that the editor is designed. It's in separate, it's in lots of different modules. So it's really like a microservices architecture. And so this core at the center of it is a small server. And then there's a front end, which is bound to your GUI platform. But the core, it doesn't, it's just sitting there responding to requests. And then the core owns the state of truth of the document. So if you open a very large file, that's in memory in the core. And the front end is only looking at a tiny window, only what's on the screen. So the front end doesn't have to deal with like scaling to very large documents. And then another part of this architecture is that a lot of the things that you wanna do, the plugins, those are in separate processes. And again, kind of wired together with this microservices style architecture. So I'm gonna walk through what happens when you press A. How does that actually end up in an edit on the screen? And kind of how does this flow through this architecture? So you start with a cocoa event, insert text A, and that happens obviously on the front end. And that turns into a JSON. So this is just pipes. This is just, the core is just listening on standard end. And you see this JSON RPC message. I'm showing kind of a simplified version of the JSON RPC, but it's still pretty simple. So it's an edit request and insert, and the string that you wanna insert is A. So that immediately is reflected in the document as a change to the document, which is a change, in this case, just to the string of the document. And the next thing that happens is that the core now dispatches, and this happens kind of this part in parallel. It's sending out a bunch of different messages to different other things that are subscribing to those changes to that document. So it sends, first of all, an update to the view and sends to the front end display these lines which are representing the view of what's on the screen. It's also sending to the plugin, the syntax plugin, very fine-grained information that says this edit changed. This edit happened to the document and where and what changed and why, because sometimes the plugin will care about that. So the view is now updated, and then the syntax plugin sends out a little bit more time to think about what color should that really be, and it sends a JSON message back to the core that has these syntax highlighting spans. So the core then basically updates its state which contains both the string and these style spans, and also dispatches to the front end another JSON RPC that says, okay, the screen, what's the view that's on the screen has changed a little bit, and then the front end displays that. So that's kind of step by step what happens in this architecture. So a little bit about the front end. I wanted to kind of explore the most modern ways of writing all these kind of apps, and so the front end is written in Swift. I wanted, even though a goal is for this to work on lots of different platforms, I did not want to use a cross-platform GUI library because I feel there's always some way in which it does not really look like a modern app, you know, like a native app, and it doesn't really feel like a native app. So it's like for each of these platforms, I'm writing a fully native front end, and the front end contains all of the logic that is specific to that particular platform, to that particular GUI, and another way that I'm achieving performance is that it's really only holding a tiny amount of state. So a lot of those things that happen, things bogged down when you have just a tremendous amount of state and memory don't happen here. And the architecture is designed so that UI event loop is almost never blocking on anything. It's always available to take a keyboard or mouse event. So when you do this asynchronous stuff, you know, just saying, oh, things can be happening here and they can be happening there at the same time causes problems, that if you have an edit that is being proposed by your plugin, either to do syntax coloring or things like inserting indentation, if you're typing at the same time, you get two edits that really conflict with each other or potentially conflict with each other. And there's a lot of literature on how do you resolve that? How do you resolve really concurrent edits that are happening from different sources? And usually people think of operational transformation in the context of a network collaborative editor. And that's a direction this could go because the engine for computing the operational transformation is pretty powerful, it's pretty general. But I'm really focusing on just solve the problems of concurrent edits that are happening because of a keyboard, a plugin, you know, all on the same machine. So you definitely, you can get these cases where things are happening concurrently, what you do, you take that edit and you transform it. You say, in order for that edit to make sense in the new state of the document, I have to transform it slightly, you know, they inserted the character here, so I'm just gonna take this and adjust it so that it now fits in the new state of the document. So I'm gonna write more about this, you'll hear more about it. I'd love to go into more detail here, but lots of stuff to cover. It's kind of a hybrid of traditional operational transformation and these conflict-free replicated data types, which is kind of a newer model. And as I say, I think there's potential for this to go a little bit deeper into collaborative editing. And another function of operational transform is to handle this kind of, when you do undo, that's also kind of a nonlinear time travel. And you can get into the same kind of conflict problems, you can get into the same things where when you do an undo, are you really restoring to a consistent state to what you had before. So another piece of the implementation is this rope data structure. So what is a rope? A rope is basically a balanced tree where each of the leaves is holding some piece of the string. So the size of the leaves is bounded and the branching is bounded, which means that pretty much any editing operation that you wanna do is gonna be log in in the worst case in the size of the buffer. If you look at something like a gapped buffer, if you're editing with a lot of locality, you'll have O1, but if you do something that kind of has to move that gap, that can be ON. So you have a thing where your average case is maybe pretty good, but your worst case can get really bad. One of the advantages of rope is your worst case is always, is always log in. So in this case, you can see the, like in the nodes, I'm storing the size, I'm storing the number of bytes of string of the children. So the implementation of this in Rust, I think here's an area where the goals of XI and the capabilities of Rust as a language really mesh beautifully. That the tree, there's a generic tree implementation and that's parameterized through traits. So there's a leaf trait and a node info trait and you can plug those in in, right now I have three completely different specializations for different ways in which I'm representing sequences and I'm representing computations that I wanna do on sequences and I think this is gonna expand. I think I'm gonna use it to store like incremental syntax highlighting state. So I'm storing the string, I'm storing the line breaks and I'm storing the rich text annotations in three different specializations of this tree. So the theory behind it is you really can represent any monoid homomorphism. I'll talk about that a little bit more in case people don't know what that is. Another way in which like the capabilities of Rust fit the needs really closely is that the API gives you this immutable data structure. So ropes really became popular in purely functional programming languages because if you have like a string buffer and you just wanna append to the end of that string buffer in a purely functional programming language you can't do that. And if you had to copy the whole string your performance would be just terrible. So that really pushed people towards this rope data structure. So you have this tree and yes you have to build up a new tree but you only have to allocate O log N nodes. And that's good. But in Rust you can do even better. If you just wanna mutate one of these ropes and you happen to be the only one holding a reference to that then there's this getMute method of this arc reference counted container that lets you just get that reference and do that mutation. And the type system guarantees that everybody else is going to see an immutable copy. So if somebody else was holding a reference to that rope it would make the copy and you would not be changing the state out from somebody. So if you were to implement this in a language like C++ you'd get the performance but we're doing a lot of kind of aggressive things here. We're doing a lot of things that in, if I were reviewing C++ code I'd see this is too dangerous, this is too risky, too much can go wrong. And in Rust you get this guarantee from the type system that it's immutable but you're not being held to that allocate everything. So you get the performance. So what is a monoid homomorphism? A monoid is a binary operator really that has the associative property and the identity problem. That's pretty abstract. So what are some good examples of monoids? A string is a classic monoid and so the binary operation is just string concatenation and your identity is the empty string. Another example is integers and then your binary operation is addition and your identity element is zero. So it's pretty obvious that these things both hold the, both respect the monoid properties. So then what is the homomorphism? Well a homomorphism is a function from one monoid to another. So a really good example is let's go from strings to integers where that function is the length of the string. So they're both monoids and this function, this string length function is preserving the binary operator in a way that like if you do a computation on one side that's accurately reflected on the other side. So when you go back to this picture of this tree then the leaves are storing the M monoid and the nodes are storing the N. So you're not replicating those leaves but at every point in the tree you know what that length is and this is generalized to anything that you wanna compute that fits within the monoid homomorphism framework and there's a few things like right now the main focus is on counting new lines and so that gives you, you know if you're storing both the string length and the new line count then you can do a traversal of that tree that's still you know log N that will give you a correspondence like where am I, what is the offset within this file for a given line and vice versa and there's actually a lot of other interesting things you can do in this framework and won't go into lots of detail now but I think this is gonna power you know some of the kind of impressive performance improvements that I hope to do. So another algorithm that is really important to get right really important to get fast in an editor is WordWrap and one of the things that I have done in XI is very aggressively making this incremental. So we'll go back to the file here and I'll do WordWrap first of all as a bulk operation and that'll take a little bit of time but not too bad and now that I'm here, you know as I type this line is actually more than a megabyte long and it is not like an almost any editor you just rewrap the whole line I mean how else would you do that but there's actually a more sophisticated you can actually see down here it says that I've touched 140 bytes when I did that and it took 0.01 milliseconds which is pretty fast. So how do I do that? I start the incremental WordWrap process at the line before the given edit because you know you can like it's possible for it to affect the line before the one where the cursor but it can't affect anything earlier than that and it keeps going until you're able to sort of resynchronize with the previous state that you had so it actually you know a lot of times it converges pretty quickly. So the result of line of wrapping, WordWrapping is stored in a line break data structure which is just another specialization of the rope of the underlying rope so this is just storing line breaks and as I said that initial WordWrap it's currently synchronous you have to do it on the whole buffer before it responds again but I wanna make that asynchronous really soon and the design I think supports that it's just a little bit more tricky coding. So many editors do plug-ins by kind of exposing bindings to a scripting language in the same process as the editor. So you basically get these data structures that get exposed of buffer and selection and cursor and so on and so forth and in XI I've decided to do it in a pretty radically different way and so this is really microservices and it's really the Unix philosophy it's really you know taking modules and wiring them together so that each module gets to focus on doing one thing for example syntax highlighting. So these plugins communicate over a pipe with JSON RPC and there's a buffer protocol where the plugin is maintaining a window into the file. So if the file is really small it's just storing a copy but if the file is really big it might store like a one megabyte window and then if you have to do a lot of processing on that then you'll do RPCs back and forth to get access to that. And so this is working today I can actually demo this again. I'll open one of the files oops this one and then again I'll make that a little bit bigger why is this not oh there it is make that a little bit bigger so you can see it and then I'll do the syntax highlighting and then you know as I type you don't see let's see I said I wouldn't do live coding and here we go should probably give that a name. If we had a syntax analysis plugin then it would have given me an error message there but not yet. So this is working and you know it's incremental and you know it's using this operational transformation so if the syntax highlighting were really slow and I continue typing then it would all be valid and then it would eventually catch up like the model of CRDT is really eventual consistency so the idea is you know when you stop typing eventually the thing will converge to the true answer. So this is inspired this idea that you can have things talking to each other is inspired there's a few efforts out there including the Microsoft language server the one that's used in TypeScript and I'm actually hoping to support that protocol directly as well as the you know kind of more specialized protocol that I've built just for communication with things like syntax highlighting and indentation and so on. So the syntax highlighting module that you just saw there this is based on Tristan Hume's syntax library so it's you know really a general purpose library and you know I've kind of just layered on the bindings to make it talk over this JSON RPC protocol and it's using it's a regx based approach it's compatible with sublime text that just it can consume the sublime syntax rules the way it's implemented internally is it's using the Onig bindings to the Oniguruma library which is the regx library from Ruby. I have implemented a rust based wrapper around the burnt sushi regx crate because you do need to support things like back references and context and stuff like that it's not quite ready yet but it's an interesting possibility to take this whole thing to rust only so there's no C bindings in there at all and hopefully like better performance because the burnt sushi regx crate uses really kind of intelligent finite state machine techniques to get even faster regx handling so it's pretty fast it's not the fastest syntax highlighting in the world but it is a lot faster than the ones that are just written in JavaScript that you see people use so one of the motivations for making plugins is you know I was looking at this as like should syntax highlighting work just be something that's in the core natively supported or should it be out there in these plugins and one reason I want it in plugins is I don't think that regx based highlighting is the future I don't think that's what I wanna do in an ideal world because if I just lex the language and had a kind of a loose grammar for it that understood like when am I in a type when am I not in a type when am I just in an expression then you'd be able to do things like look at an angle bracket and say well is this a bracket or is this a comparison operation and so I think that has a potential to be both faster and more accurate just strictly better across the board and I'm kind of excited about the potential of having a syntax highlighting module be used in context other than just driving syntax highlighting of XI editor so let me show you kind of you know how would that work like what would you do so let's build the highlighting plugin take a little while to compile no problem and then I'll go and I'll just run it it's now it's listening on standard in so then I will open up my RPCs and I'll take a little bit of JSON and what this JSON says is I'm gonna initialize a new buffer and size of that buffer is gonna be 16 bytes and so it says an RPC that says oh give me the data for that buffer and with a maximum size of one megabyte so if that was a huge file it would be saying give me the first megabyte of that file so okay fine sure pubf and main good and it says here are your color spans and so then if I want to say oh edit that make that start a comment after that then I just send another RPC that says edit insert if here's where you're editing here's the text that you're adding and so sure here's your new color spans so it feels to me like a lot of things could potentially be talking to this plugin and you know it could be a really kind of general service and as we get like higher quality and higher performance I think it could be a very valuable library and like people that need syntax highlighting as a service you know makes sense shots you heard here first and you know given like where Rust is going like you could even imagine compiling this to wasm using inscription and running the whole thing and deploying the whole thing to the web so the RPC how does this work so it's based on JSON RPC and this is one of the most controversial like the first you know I say this is based on JSON people say why JSON that's so inefficient so slow it's actually not inefficient JSON implementations tend to get optimized like crazy so if you were just defining a binary protocol and you just started writing code that would probably be slower than the actual Serde JSON which has been through a tremendous amount of evolution and optimization and anytime you're writing a plugin and you're like oh I have to do this RPC layer well doing it in JSON that's very easy I mean you talk about batteries included this is a double A battery not a CR123A so the current code is using threads and like so there's this thing where you're blocking on input and you actually want to be a little bit more sophisticated especially in the syntax highlighting plugin that you know you're thinking and if somebody presses a key you get an edit event that comes in you actually want to interrupt you're doing this in chunks you don't want to do it a line at a time but if you see an event coming in you want to be sensitive to that and so there's like a method that says you know is there a request pending and then you pull that like really you know every time you actually do a line and the way that's implemented is that there's a separate thread that's blocking on input and it sends a message over a channel to the thread that's actually computing your syntax highlighting and then it's using ARC mutex all over the state every you know that anytime you need access to some piece of data then you do you go through an ARC mutex to get that and that's not the future this is the future that I really would love to replace this with using the futures library which we just saw and that would have some pretty significant advantages that this that moving just moving an object from one thread to another thread and crossing the Linux syscall barrier to do that is between five and 10 microseconds and so by doing it with futures you actually are going to save that overhead altogether and another thing I'd like to do is you know this idea of a future this idea of saying okay here's you know a request to do something and I'm not ready with the answer yet because maybe it's an RPC over here maybe it's a slow computation the right model is here's the request the result is a future with the result and that that I'm I think can be kind of a metaphor right now this is kind of coated by hand but I think that can be an organizing metaphor for this and I think I want to refactor it in such a way that the zycore can be more embeddable in other apps and not necessarily even dependent on JSON RPC that just becomes a detail so switching gears a little bit another component of zy is this Unicode library and right now the main thing that's in there is this line breaking algorithm so UAX 14 has all the rules about like if you have a combining character then that's not a line break but if you have a space it is and you know it's actually Moji has a whole set of rules it's very complicated and you know the industry standard implementation of this thing is ICU you see that almost everywhere and of course I wanted to do it my own way and so I built a kind of it's at the heart of it it's a state machine that you know you character it just runs through the string character comes in what Unicode class is it advances state machine by one state and then it says either this is or is not a line break opportunity and the implementation turns out to be three times faster than ICU because I've just you know focused relentlessly on the core what does this thing really need to do and the API is designed to support that incremental so it's the incremental breaking so you know obviously you get an iterator it's a very natural very clean interface so that you only run it as far as you need to and then you initialize it with some state which might be in the middle of a line and just run it only as much as you need and so I'm hoping to do more with Zayuna code there's a bunch of interesting problems that need to get solved I think the most interesting of these is the case insensitive find and this is actually kind of where it touches in to the homomorphism stuff that if you're doing case insensitivity for completely arbitrary languages that's a really tough algorithm you know because there's these case transforms and there's you know different normalization forms 90% of the time especially when you're dealing with a really big like dump of something like you know that ninja file that I opened before it's ASCII so I'm hoping to use a homomorphism to say like a sort of a difficulty level and say ASCII is the simplest difficulty level and then there's more that might tell you oh you have to use these more complex more expensive algorithms to do case transformation so when you're doing search you know you're going over the tree say for this whole subtree I can just use this really really fast you know just run over bytes and you know and with 20 with not 20 I guess to do the case transformation and then if it's you know if it's a complex language that needs more complicated case transform rules and you say okay go the slow path and I want to make sure all this stuff fits inside Unicode like some of this kind of makes more sense to be I mean there's some Unicode functions in the Rust standard libraries although I think the kind of the tradition now is you put things like this in a crate rather than the Rust lang you know core crates and Unicode RS so you know I'm talking to the people there and I want to make sure that like the stuff that makes sense to go in there the improvements go in there and that really you know brings us to this whole question of community involvement this has actually been one of the more gratifying things of working on this project that within a week when I put this on GitHub I got two prototype front ends that worked I think that was the GL and the Windows based ones and those are kind of coming along and then the syntax highlighting pro I was getting going pretty far down the line of writing syntax highlighting myself and Tristan Hume kind of came up with the syntax library and it's like you know hey it's done that's awesome and so I have 27 contributors total you know with lots of different features and fixes and improvements and lots of really great discussions that go on on the GitHub issue tracker as well and the process of doing this you know like Serde JSON didn't escape control codes correctly so that was really cool interaction you know like here's a fix and well the discussion is this the best way is this going to regress performance and then that got merged you know some of the like Unicode property lookup you know I use tries that's faster than binary search so pull request to rustling to get the Unicode property lookup faster and then the line breaker like it was really good timing because you know Servo had this thing that was just looking at spaces it didn't really use do the Unicode rules right and I was like hey do you need this and they're like yeah this would be great and so again getting that integrated into Servo happened within I think like a week or two of the project even going up to GitHub at all so you know I really enjoyed being part of the Rust community so I hope we have some time I think we do maybe a little bit for questions you can take one question we're actually a little bit over time already but I'll give you one question so you get to pick who that is oh my gosh I think your hand went up first all right now I'm nervous because they might have better questions I'm actually really curious about the your scrolling of the 300 meg file what it could you just describe the protocol to actually like scroll and refresh is that like a deltas are you shipping deltas back and forth or what so that's actually interesting I was debating whether to have a slide for that so as you scroll so the thing is that the core is maintaining state of where that scroll region is so when you're not scrolling if you update it it sends like that screen full of information so it's most one screen full of information as you scroll obviously you've got state that lives in the core that doesn't live in the front end so it sends at that point that's like one of the few synchronous RPCs in the system and it says give me the view, the rendered view so it's got all the line breaks and style spans in there give me the rendered view for this region and then that's an RPC that goes out and it comes back as JSON lines with spans and gets displayed and then I won't show it there's like debug information that's almost always less than one millisecond roundtrip to make that RPC happen so even though it is synchronous it's not slowing down the scrolling process you're sending the screen full of information which is not that much oh no it's a window I mean it's a it's there's like a yeah no it's more sophisticated it's like a delta of what you actually need yeah and it's chunked so it's not you don't do it every line it's like every screen full awesome thank you so much the talk was really great thank you