00:00:00.179
okay so uh the title of the talk tonight is how I built my code editoring Ruby
00:00:06.600
so um let's go get started with this question right away how did I build my code
00:00:13.920
editor in Ruby so yes I used Ruby as a programming language using Jr Ruby
00:00:22.500
I applied object oriented design with abstractions like file and
00:00:27.840
directory I applied architectural patterns like MVC and MVP
00:00:34.260
I applied design patterns like command pattern composite adapter and observer
00:00:41.340
I use domain specific languages like the glimmer GUI DSL
00:00:47.879
I decomposed software modules meaning components or custom widgets when
00:00:54.120
building GUI I reused ruby gems Ruby libraries like
00:00:59.760
glimmer DSL swt file Watcher and clipboard and That's all folks I give you the
00:01:07.200
answer I'll go home now just kidding that was just a preview of
00:01:13.020
The Talk hang on for more okay so about myself I have a Bachelor
00:01:19.920
of Science and computer science from McGill University in Montreal uh uh and master of science in software
00:01:25.920
engineering from DePaul University Chicago like Mateo mentioned I've spoken at a
00:01:32.880
number of conferences uh I've recently won the fukuokai Ruby 2022 special award uh by mats the
00:01:40.860
creator of Ruby and uh I work as a senior full stack
00:01:46.439
developer at lexap so uh why build that code editor in Ruby
00:01:53.100
so let's start with the why the first reason is Ruby is a great language
00:01:58.920
it's object oriented it's dynamically typed
00:02:06.299
it's inspired by lisp Small Talk Pearl Python and basic
00:02:11.640
so that means it's got I mean small talk is one of the first languages that
00:02:17.520
demonstrated uh very a very high level of object oriented programming so everybody has some of the best style of
00:02:24.660
object-oriented programming in the world so lisp demonstrated how to build embedded dsls like embedded languages
00:02:31.500
within list in order to become a lot more productive at building Ruby has the
00:02:38.420
Pearl has a lot of good uh terminal or command line support especially for
00:02:44.940
opening files and running commands and stuff like that so Ruby has that python
00:02:50.400
has nice syntax that Ruby inherits uh some of and basic also is known for
00:02:55.860
being very approachable by non-programmers so Ruby has that as well
00:03:01.280
Ruby as a result supports meta programming and embedded dsls like I
00:03:06.720
mentioned just like lisp so it's very expressive it's highly
00:03:12.180
expressive and it gives us very maintainable code so that means it's very productive like
00:03:18.239
rails rails is famous for being one of the most productive Frameworks for web development
00:03:23.580
in Old programming languages nowadays um a second reason is I'm a senior
00:03:30.060
software engineer I have about 19 years of software engineering experience I've
00:03:35.640
also I mean I've been programming since I was a kid even before I mean I have a 19 years of professional experience but
00:03:40.920
I've been programming since I was a child so I have over 34th picky almost I
00:03:46.500
mean not 40 but over 30 years of experience in programming so why would I not build my own code editor I'm
00:03:53.640
proficient in requirements engineering meaning how to take requirements and turn them into
00:03:59.000
analysis and design that results in software um professions and object oriented
00:04:05.280
programming proficient and Architectural patterns design patterns domain specific
00:04:10.560
languages writing highly maintainable code um automated testing so I mean I I one
00:04:19.680
day I woke up and I'm like why why why am I using somebody else's editor why not build my own editor and the other
00:04:26.040
thing that bothered me was why are all editors not written in Ruby Ruby is one
00:04:31.320
of the best programming languages in the world editors code editors should be within Ruby too
00:04:37.380
so that's how Gladiator was born it was an open source project that was
00:04:43.320
born out of the idea of exploring Ruby in order to build my own code editor so let's start by doing a demo of Gladiator
00:04:51.120
so I'm going to get out of the presentation mode and launch it this is the icon Gladiator
00:05:00.840
so it just gives me this button that says open project then I can select a project like Like Gladiator itself
00:05:11.699
there it is so um basically it it has a syntax
00:05:18.479
highlighting for over 30 programming languages um and what you're seeing here is Ruby
00:05:23.759
codes and this is the code of gladiator itself uh it's got find and replace support so
00:05:30.180
I mean I can use any keyword and look for occurrences of it like like that for
00:05:35.639
example and I can do replace I can do it make it case sensitive or not or not I can look up files so if I want to look
00:05:42.539
up a model I type model and it gives you know all the models and then like navigate to this model that directory
00:05:48.120
model um I can I have a tree a file tree
00:05:56.639
navigation on the left side over here it's a file tree explorer that lets me navigate all that
00:06:02.400
the elements of the project so it's got binary scripts for launching
00:06:07.680
the app it's got configuration icons images most of the logic for this
00:06:13.919
Library art is under lip and it's got views models
00:06:19.560
um a launcher script and the main entry points of the project
00:06:26.940
um what are some other features uh oh yeah one cool feature is I can open a like a scratch pad
00:06:32.639
and for example type in a script here and say let's just say
00:06:38.479
text hello world label
00:06:43.740
world and um
00:06:49.500
I can run it unfortunately Stick Run so what happens at demos and uh yeah so
00:06:58.680
I'm supposed to be able to run a ruby script I don't know why it's not running but I'll leave that for another time but
00:07:04.800
uh let me open another project so I can open multiple projects
00:07:13.819
and uh this is a ruby script let me try to run this one okay cool this one ran
00:07:18.840
so this is another app that it's just a demo app of how to use a syntax
00:07:23.880
highlighted text editor it's called hello code text and I just opened it
00:07:29.520
um and decoded the app is here the stat that I just opened
00:07:34.620
so let's get back to the main project close this
00:07:42.419
um yeah I mean I can right click a file here and delete it refresh the tree create a new directory
00:07:48.720
a new file I can do copy paste and then we have a menu on top
00:07:54.419
uh open scratch Pad open project uh standard edit menu with the undo redo
00:08:00.479
copy cup paste um oh yeah this is cool actually split pane so I can split
00:08:06.900
split the pain vertically or horizontally actually I can also use the
00:08:12.720
keyboard shortcut so I can do this this is good because then I can open tests for example like specs
00:08:20.460
so let's just open this back on this side that way I can put this back on one side
00:08:26.759
and there it is the file is on the file
00:08:33.120
model is on the left side in the file spec us on the right side so it helps me
00:08:38.159
be more productive um but there are many other shortcuts like
00:08:44.039
I can yeah I just showed you this I can minimize the coding area to basically
00:08:50.160
hide all of those extraneous panes so and then I can even focus on one of the
00:08:56.040
two sides so I just focused on the test side and I can also close it and come back to
00:09:01.620
this side and and come back to this or I can just open the file uh find and replace alone
00:09:08.820
and I can use the mouse to like do this or yeah so um it's very basic functionality
00:09:17.399
for what a code editor is um I think I pretty much covered everything
00:09:23.040
I mean you guys thought tabs so I have multiple tabs for opening multiple files uh when I'm navigating a file I get some
00:09:30.839
sort of analytics here about like I mean it's not exactly analytics more about what is the character position what is
00:09:37.019
the line number Etc uh I can jump to a line like without hundreds or
00:09:43.200
hey there so it's a lot it's very basic coding functionality I built this in the
00:09:49.140
year 2000 2020 story um I've been using this for about three
00:09:55.019
years now as the main editor for all of my all of my work open source or closed
00:10:00.899
Source um so it's good enough for all my personal needs
00:10:07.079
um and I'll talk more about how I got into that one
00:10:12.600
any questions before what library do you use to render the UI
00:10:19.620
I'll I'll get to that one I'll answer that in a bit any other questions is it
00:10:25.380
uh across OS if I'm on Linux uh yes this one's on Windows 2 and it runs on Linux
00:10:31.079
with some hiccups okay so it's designed primarily for Mac because that's where I use it the most
00:10:37.320
but I've been able to use it on Windows Analytics as well that's pretty cool that's because of whatever yes thanks
00:10:45.959
so um okay so what is the Gladiator software
00:10:51.180
architecture before we dig into the code and the design and all of that what is the general architecture uh so it's a
00:10:57.600
it's a GUI app it's by GUI our main graphical user interface so uh Graphics
00:11:03.300
or interface apps uh our pop usually in general use the MVC pattern
00:11:10.279
which came from Small Talk the Small Talk programming language uh because
00:11:15.839
it's uh it provides a very clean separation of responsibilities so usually uh in a GUI app you have a
00:11:22.440
screen that gives you a sort of the user interface with items on it
00:11:27.839
and then you interact with the screen using a control device devices like The Mouse and the keyboard
00:11:34.260
so that's the controller part of MVC so you have the view which gives you the uh
00:11:39.779
the screen you have the controller which lets you control the app with the keyboard and mouse
00:11:44.880
and then the model is basically the business model behind that app that you're building it's basically how do
00:11:51.060
you uh what are the concepts of the app uh so I mean in Gladiator obviously some
00:11:57.060
of the concepts that are very obvious are we have a text editor uh we have text editor tabs we have a file explorer
00:12:05.459
tree etc etc I'm going to get into that in a moment
00:12:10.519
I'm also applying something called the MVP pattern which is a variation on MVC
00:12:16.459
that makes the controller a presenter so the controller is not only about
00:12:21.600
controlling um making changes uh through the keyboard and mouse to the to the view into the
00:12:28.860
model of things that are recorded in the model but also it's about observing the model for changes and making them get
00:12:36.360
reflected in the view automatically and that's done using something called Data binding or bi-directional data binding
00:12:42.839
data binding lets you basically declaratively bind certain elements of the view into certain properties on the
00:12:50.760
models I'll show you quote examples of that
00:12:56.060
thirdly the app is decomposed into software and modules AKA components AKA
00:13:03.060
custom widgets so Gladiator itself is a component
00:13:08.339
meaning if I want I can embed the entirety of gladiator into another app and I can bring it up when needed just
00:13:14.339
to edit some code but also it's decomposed into smaller
00:13:19.380
components like text editor file explorer tree file lookup list
00:13:25.019
the file edit menu the Gladiator menu bar and the progress shell
00:13:32.600
one thing I want to note is these are the general architectural Concepts behind building
00:13:38.279
Gladiator but it's not in any way or shape or form completely perfectly and
00:13:44.399
cleanly adhering to them the Code you know doesn't it's not a completely clean
00:13:50.160
MVC but that is the general concept that I built Gladiator around
00:13:57.360
as far as the software development process what happened is I basically made this
00:14:02.880
challenge with myself of not using any Editor to build Gladiator except what
00:14:08.100
comes built in into the operating system and on the Mac or Linux in the terminal I have Vim so I only used Vim to build
00:14:16.560
Gladiator initially secondly I started very very small and built features incrementally and I
00:14:23.639
started with the smallest thing possible that would enable me to upgrade myself from them to something better so that's
00:14:31.079
what the third step is and then I started eating my own dog food and using Gladiator to build more Gladiator
00:14:36.660
features so initially I had nothing but the text editor I didn't even have tabs I had just the text editor and I might
00:14:44.339
have had the file three that's it eventually I added a file lookup list
00:14:49.440
and then I added tap support and then I added split pane etc etc uh and that's how I'm continuing
00:14:55.980
development on this project for for three years I mean I touch it once every few months I'm not working on it every
00:15:02.579
day or anything except in the beginning I might have worked on it every day for day or two in the beginning uh but now I
00:15:08.699
touch it every few months and whenever I need a new feature I upgrade Gladiator with a new feature
00:15:15.720
as far as the software development process I use something called the glimmer process which I came up with when I built another open source Library
00:15:22.199
called glimmer it's simply made up of seven guidelines to pick and choose as necessary it's not very dogmatic you can
00:15:30.540
rule out some guidelines if you want or not apply everything and you don't have to follow an order
00:15:36.740
it's just a summary of all my learnings from my software engineering work as
00:15:42.180
well as my masters in software engineering so I mean there's a requirements Gathering phase usually in any app meaning writing down a list of
00:15:49.079
tasks or features that you want it doesn't have to be complete complete but you need to have something to start
00:15:54.899
with uh and usually you do prioritize prioritization of this list uh you need
00:16:01.139
a sort of a general architecture and software design uh an initial release plan meaning what do you want to release
00:16:07.139
for version 0.0.1 and then what do I want to release for the MVP the very first uh
00:16:13.500
beta Alpha release and then the very first Beta release Etc so you need some sort of a release plan
00:16:19.980
you need small releases so no release took me more than I would say more than
00:16:25.139
two weeks per release and to be honest in general they were usually two days that may be produced
00:16:37.440
this case so I can tell myself what I don't like and what I like uh and automated tests uh I did not so I
00:16:44.459
did not write a lot of automated tests this project was more I'm using it every
00:16:49.500
day so that is the test I don't need an automated test but this is a good example of why here I I don't recommend
00:16:55.199
to be dogmatic about any of those guidelines but I do have a few automated tests there were a few parts that were a
00:17:00.720
bit buggy that I ended up resolving the bugs with by running tests for them any questions before I move on
00:17:09.179
yeah do you ever open it up to outside intestines I did yeah I have a few users
00:17:16.740
for the project and they've reported some bugs to me in the past mostly what to do with Windows because I don't use
00:17:22.380
it on Windows much maybe not just bugs but maybe I'll sold potential features or improvements
00:17:30.299
um well I'm hoping we'll have a lot more users after today yeah
00:17:38.280
and the refactoring of course uh that's a very important Point uh every like
00:17:44.760
often I find a feature in a very big blob of code but then a month or two later when I try to add more features on
00:17:51.360
top of that becomes very difficult so I end up refactoring and however I follow the school of like I only refactor as
00:17:58.080
much as needed to facilitate future work I don't try to immediately refactor it into the perfect structure so I refactor
00:18:06.120
is needed only but that's been always always important refactoring
00:18:12.179
um I mean I refactor almost every release so uh let's start digging into the
00:18:18.120
implementation of gladiator features so uh first let's get into the file tree navigation
00:18:26.820
so um I have the code over here
00:18:32.700
um the file tree navigation is basically this part on the top on the bottom left
00:18:39.960
um it's simply a tree structure of the directories of the project that I opened so in this case the project that I
00:18:46.080
opened shows up over here that formed an absolute path of it it's basically uh
00:18:51.840
you know whatever some absolute path code and then Slimmer uh glimmer Dash CS
00:18:57.299
Dutch Gladiator which is the name the official name of this project so
00:19:03.960
uh how did I build this part so this part has been decomposed into its own component eventually and right now it's
00:19:10.380
called the file explorer tree so we can just look at the code of file exploratory to know how that that is
00:19:15.539
implemented so I'm using uh glimmer as the user interface Library
00:19:20.840
glimmer DSL for swt lets you use the swt
00:19:27.120
library which is the same library that Eclipse IDE is built with so I'm using the same technology that was used to
00:19:33.419
build the Eclipse IDE in order to build this editor and that technology is
00:19:38.460
written in Java so in order to use Java Java libraries from Ruby you have to use
00:19:43.500
Jr Ruby instead of C Ruby so I'm using jruby as well um so that's how I've been able to
00:19:48.660
leverage swt and I've leveraged it using the glimmer DSL for it which gives you a
00:19:55.500
ruby way of dealing with swt as opposed to the javaway the Ruby way gives you a
00:20:01.320
DSL that maps to the structure of the of the graphical user interface and usually
00:20:06.900
whenever you build a component you need to extend if you're building a custom widget custom components you need
00:20:13.620
to include this glimmer column called an UI phone call and custom widget module or mixin
00:20:21.440
and after that you declare the body inside this body method not method it's
00:20:27.539
actually a body sort of structure so body lets you build
00:20:33.659
the graphical user interface as a hierarchical structure so think of this as the Ruby version of HTML HTML lets
00:20:41.039
you open a body and nest in it divs and then within the dips you can Nest paragraphs and within the paragraphs you
00:20:47.100
can Nest labels if you want and input text Fields Etc this is the same thing
00:20:52.559
done in Ruby using a DSO um so here the the rules of Ruby style
00:20:58.620
guides change a bit you try to think if it's declaratively not imperatively so
00:21:05.280
when you're writing imperative code in Ruby and anything you have a method that you're invoking when you pass it a Blog
00:21:11.880
usually say do end and because you're being imperative you're saying okay this is the method uh
00:21:18.000
print and then do and within the do maybe you want to do extra work before
00:21:24.120
printing some text and then end that's because you're writing imperative code here I'm writing declarative code and
00:21:30.600
I'm taking advantage of the fact that in Ruby I can write dsls and DSL is basically a language embedded within
00:21:37.020
Ruby that lets me write graphically or interface code so I have a body
00:21:42.539
uh within the body of this component the first thing that appears is a tree and this is the tree that you guys are
00:21:48.960
seeing over here and then within within the tree so whenever you open a uh so usually I mean
00:21:55.500
just to give you an example an HTML I would have done it like this I would have done uh body
00:22:02.340
and then within that I would have done there is no tree component in HTML but if there were
00:22:08.100
that's what it would have been and that's how I would have done it the same exact code in Ruby would do it like
00:22:14.280
this he would say this uh you open a block and you close it and then you say Tree open a block close it it's a lot
00:22:20.340
more it's a drier because I don't have to have a closing tag you just use a curly brace to close it
00:22:27.600
um and then within the tree you can declare properties of the tree so let's get back to the code
00:22:33.659
whoops Maybe do you have a way to zoom in just a little bit for the folks oh yeah yeah
00:22:40.020
let me zoom in yes okay
00:22:46.440
so I'm gonna kill those Okay so
00:22:52.460
thank you is that better yeah not that much cool okay so uh the body is a tree element
00:23:00.600
and then within the tree uh we have the Tree items we have a foreground color uh
00:23:05.880
we have some basically declarative stuff about how to support drag and drop from this tree
00:23:12.059
which I'll get into in a moment and then we have a menu so under the tree we have
00:23:17.159
a menu nested meaning the menu has open deletes refresh screening so this is basically if I'm gonna do unzoom for a
00:23:23.940
second and zoom back here this is what happens when I right click on one of those elements and um
00:23:30.179
uh you get this menu it's got open and delete refresh Etc so the code structure
00:23:36.059
mirrors the GUI structure meaning you know the menu belongs to the tree so
00:23:41.340
here you Nest the menu uh within sorry I must be at a different top I'm sorry
00:23:47.520
go back uh here this one yeah so here we have a tree and we asked
00:23:53.640
the menu within it and this is very similar to HTML HTML you basically have for example a form and within the form
00:24:01.740
you Nest an input field input field and the input field belongs to the form here's the same way it's basically I
00:24:08.340
have a menu that belongs to this tree and it'll pop up when I right click on the elements of the tree
00:24:13.820
so the Tree items is basically how I'm populating the tree and this one line is
00:24:20.039
populating the tree using data binding so all I'm doing is I'm basically binding the items of the tree to the
00:24:28.440
project directory object attribute sorry on cell
00:24:34.679
um and uh here I'm specifying that in order to grab children from that object
00:24:40.140
you need to invoke the children method and in order to grab the text the string
00:24:46.320
that will be displayed for every child like what is the file name for example uh
00:24:52.320
basically uh I'm invoking the name method so
00:24:58.380
um by using data binding um I've reduced the work of populating the
00:25:05.640
tree into a single line of Rubicon that's it which is unheard of in other languages and Java you have to write
00:25:10.919
patients and pages of code to build a GUI like that like I'm pretty sure that Eclipse IDE itself has pages to pages of
00:25:17.039
code to do stuff like that so because I used to do Java development of GUI in the past so so that's why I love Ruby I
00:25:24.059
mean in Ruby this is a single line of code it takes care of populating the Tweet declaratively why because if you
00:25:30.240
think about it uh what is a tree a tree is simply a hurricane
00:25:35.279
that begins with a root element and uh it ends with leaves at the bottom
00:25:42.299
so how do you data bind the tree the simplest way of thinking about it is I need a root element first of all second
00:25:49.200
of all I need to uh to know what what method should I invoke on this root element to get the children of the of
00:25:54.299
that element recursively and basically the method is
00:25:59.400
children I mean I ended up having a children method literally on that object
00:26:05.940
um and then on every child once I grabbed a child how am I going to display the text in the tree uh item for
00:26:13.080
it well I call the name method so that's all you have to do um using data binding declaratively
00:26:19.740
that's basically all this information that I just stated is here in one line of code
00:26:25.500
um and by the way tree elements are follow the composite pattern in
00:26:33.480
object-oriented programming meaning every element is a composite of more elements and every sub elements the
00:26:39.059
composite of even more sub elements um that's called the composite pattern and they all basically have to extend
00:26:45.960
the same class so all the items of the tree have the same story super class which is a composite sort of super class
00:26:53.580
uh and if I were to dig into the model so the root object is the project directory uh the model is a directory so
00:27:01.020
if we were to look at the model layer we have a glimmer Gladiator directory
00:27:08.460
and uh the directory has a number like
00:27:14.400
basically has a lot of attributes but one of them is children that's the attribute that I configured to be
00:27:19.980
loading the tree in the GUI um it has a selected child as well
00:27:25.020
because when when I open a file like this one that's the selected child wipe out so every composite has to know which
00:27:32.279
child is selected underneath um and there's a lot more information I'll get into it later but that's pretty
00:27:39.779
much the gist of what a tree is and how I how I was able to populate the tree uh
00:27:45.240
I basically made a directory here that uh will populate itself with an initial
00:27:50.700
path which is the root path of the project and then from that point on there is
00:27:55.799
logic on the model that will let you grab the children and behind the scenes it will call the Ruby facilities that
00:28:02.880
will show them to the command of line and figure out what files are under that directory as well as what other
00:28:08.820
subdirectories are under that directory um
00:28:14.820
so that's pretty much it I mean the menu items you got the gist of that too forever so
00:28:21.779
there's a menu it has menu items underneath uh within every menu item there are properties as well like what
00:28:27.899
is the text of the menu item uh and then there's a listener so this is uh this gets into the Observer pattern from MVC
00:28:34.919
uh usually um uh an MVC uh well let's get back to the
00:28:41.520
diagram of MVC it might be helpful to get into it right now actually let me just jump into it right
00:28:48.059
here okay usually The View
00:28:53.360
anything you see on the view is a visual representation of the model data so the
00:28:58.799
view is usually observing the model for changes and then it gets updated some model data that's how MVC type the
00:29:06.720
clean Small Talk At UC works the proper one and then the controller on the other hand is observing what the
00:29:13.919
user wants to do with the keyboard and mouse and when the keyboard and mouse are triggered
00:29:19.020
um uh it actually will end up uh calling the model to cause a change and then
00:29:24.659
since the view is observing that model for changes it'll it'll get updated indirectly after the user had made a
00:29:30.720
change with the controller so um
00:29:35.760
so here what I'm trying to say is basically you can configure listeners on menu items to tell to tell uh which are
00:29:43.140
basically playing the role of controller following the Observer pattern and this
00:29:48.899
listener the block of code that's underneath it will get triggered when I click on the menu item so I mean if I
00:29:55.140
click here and I say we're let's just say rename that's more obvious it let me rename the file for
00:30:01.860
example so that's because it responded to one of those controller uh actions that I have
00:30:09.779
configured over here um
00:30:19.440
I probably should you name it back though
00:30:24.840
okay um yeah so
00:30:32.520
so let's I'll get into a lot more detail later let's just jump into a few other components from uh Gladiator just to
00:30:40.740
uh yeah let's talk about something else if I look up my name so if I look up my name is this part
00:30:46.679
so how is it built again it's a component uh I have a file lookup list
00:30:51.840
component here it's got a body and again just like HTML except with a much lighter version of HTML and it's not
00:30:58.679
HTML because it's desktop development so it's more of a I call it the glimmer GUI
00:31:03.720
DSL it's a DSL for a GUI development it's called glimmer so I'm using the GUI
00:31:09.419
DSL here the glimmerically DSL and I have a list in this case
00:31:15.779
this is a list that will show me files that are looked up using a text control
00:31:21.059
so here are typed models so it basically did a search that showed me all file paths that have
00:31:28.980
the word model in them but I could have typed a view that would have given me everything that has view in its path and
00:31:35.460
and other Ides usually this feature is hidden you usually have to type a shortcut like command l or something and
00:31:41.640
it'll pop up I chose the design of keeping it shown all the time because I thought it might be more user friendly
00:31:47.700
I'm being my own usability expert on my phone now that might be just my own opinion maybe most people like disagree
00:31:53.880
with me but just an example of uh you know when you're building your own app uh you eat your own dog food and you
00:32:00.179
can build the app exactly exactly the project is open Souls yes so
00:32:05.760
I could forward you can make it absolutely so absolutely I agree with your opinion thank you
00:32:12.840
um so yeah so it's a list um it's got a selection that will be
00:32:18.240
um where we are so the list uh selection attribute uh will will basically data
00:32:26.460
bind whenever I select an item in the list it'll end up storing it on this filtered path attribute on this object
00:32:34.080
project directory um using data binding so in data binding
00:32:39.779
is usually bidirectional meaning when I first load the list uh whatever is
00:32:45.240
stored on that project directory will show up in the list but then if I make a
00:32:51.120
selection it'll it will store a value back on the model so that's why it's called bi-directional Data binding
00:32:58.500
um it's a bit Superfluous to say it because it's a bit it's intuitive usually you
00:33:04.019
want to make a change to the uh the codes and you also want to see changes
00:33:09.480
reflected back to you so you almost always you want to do something bi-directional um
00:33:15.779
and again it has a few properties selection foreground and then it's got listeners listeners always begin with
00:33:21.419
the on keyword so on Mouse up on on key pressed so on keep rest is basically if
00:33:27.659
with the keyboards I use the keyboard as a controller and I click enter it'll
00:33:33.539
open that file like right now I am on that file but let's just say I pick a different part of this one see it took
00:33:39.240
me there um so on Mouse up we'll we'll actually change
00:33:46.559
the selected file on the project directory
00:33:52.080
that is open over here so basically that one is if I click on a file so let's
00:33:57.179
just say I click progress show it'll it'll also open it's here so
00:34:03.080
basically this is happening indirectly using the MVC pattern that's what makes the code kind of clean the code is a
00:34:09.300
single line of code like this single line of code couldn't happen done all the work for
00:34:16.339
figuring out the path opening the five contents and opening up a new text editor and then
00:34:23.339
showing it in a new tab there's no way that line of code did all of that well that's true because that line of code
00:34:29.339
isn't doing all of that directly but it's doing it indirectly all I'm doing here is I'm updating an
00:34:36.000
attribute on a model and saying this is the new selected file however because I'm following NBC I have
00:34:43.679
codes elsewhere that says observe that attribute on this model and do all the
00:34:49.440
work that I just mentioned which is open the file contents open a new text editor load it up Etc but the code here is so
00:34:56.220
clean like if I'm maintaining this code it's a single line of code like I'm not thinking of all the other details at the
00:35:02.760
same time so that's pretty much that this is drag and drop drag and drop is a bit
00:35:09.359
complicated but it actually got simplified in recent in more recent versions of glimmer uh still this is not
00:35:15.240
that complicated it's basically saying that if I drag anything from here send this data for dragging basically set the
00:35:21.720
path of the file that's it set it as the event data and then when I drop the file
00:35:26.940
somewhere else there's a Code elsewhere that's saying if I drop so let's let's do drag and drop
00:35:32.640
so if I drag this text editor and drop it here it basically split the
00:35:37.800
screen and open a new editor here so there's another code elsewhere that is waiting for that drop to happen and it's
00:35:44.160
actually in Gladiator if I'm not mistaken or let me double check it might be in text editor
00:35:50.520
oh yeah these are synced using using bi-directional data binding so that's why they both scrolled at the same time
00:35:56.880
I built it that way some people might not like to build it you might want to separate the the
00:36:03.540
um the scrolling but I I I actually kept in sync synced for me it's good enough like
00:36:09.359
that um but if I were to dig into this uh the
00:36:15.119
text editor has there it is there's a drop Target declaration here that's saying if I drop something here
00:36:21.960
um grab that data that was the event data and then set it as the selected file on the project directory and again
00:36:29.339
there's then code elsewhere that is monitoring that selected chart path and as soon as it gets set it creates the
00:36:36.720
text editor and it opens it and if it's a drop event it'll automatically detect that it's a drop event because I'm
00:36:42.300
setting this attribute drag and drop true and it'll split the screen for me whereas if it was not a
00:36:48.000
drag and drop event for something else I would have not split the screen it would have just opened a new tab
00:36:53.160
so let's move on so text editor you already saw with the text editor a little bit
00:36:59.640
this is the text editor basically it's got a number line numbers part which is you see material on the left side
00:37:06.359
uh and it's a styled text widget meaning text that could be styled just like HTML
00:37:12.480
think of it as just like HTML but it's it's in a desktop context
00:37:17.700
um and uh I'm data binding a whole bunch of attributes here and the key one is
00:37:23.460
basically uh top pixel uh let me see
00:37:35.160
Waits I might be pointing at the wrong attribute um actually I can just search for it at
00:37:41.220
the time thanks for watching yep there it is uh oh okay okay I'm doing it in a
00:37:48.000
different place never mind load line numbers text content and then here I'm basically basically what you can do with
00:37:53.220
glimmer is reopen any component later if you want and add more stuff to it like more Properties or more elements within
00:37:59.400
it uh so just like Ruby's how Ruby supports open classes glimmer supports open GUI objects like GUI components it
00:38:06.599
can always be open and going components and change it so I'm changing it and basically synchronizing with
00:38:11.640
bi-directional data binding the topics so value to the topic cell attributes on
00:38:16.859
a model called the file model and that will synchronize so basically when I scroll up and down the top pixel is the
00:38:23.520
distance of the pixel that you're viewing on top here from the beginning of the document so for example here the
00:38:29.700
top pixel is oh yeah by the way we're showing it here so here the topic sold 402
00:38:36.000
um if I scroll down more it gets it grows even bigger now it's 1463. uh so that's how I end up thinking the
00:38:43.440
line numbers on the left side with the code on the right side so basically I have two text areas one and a little two
00:38:49.079
style text areas one that is representing the line numbers and one that's representing the code uh the code
00:38:55.200
is the text area it's called code text which is a more advanced version of style text style text lets you style a
00:39:02.099
text area any way you want context will automatically infer The Styling from the
00:39:07.619
language that you're using so that's why I specify to it the language um I specify here the language that I'm
00:39:14.579
using in this case it's Ruby it'll actually use the model to figure out what the language is so based on the
00:39:20.099
file extension if the file extension is dot CR that's the crystal programming language if it's dot uh RB it's the Ruby
00:39:27.780
programming language so for example if I were to open readme.md that's MD that's markdown so that's a different uh I see
00:39:35.339
like it also has uh coloring but it's different this one's more focusing on markdown syntax like building URLs for
00:39:42.480
example links um so again I'm just giving you a very high
00:39:48.599
level overview of this project um I'm not like this is uh I this is a very
00:39:55.320
Advanced I would say uh presentation and I'm Gonna Leave You with the high level
00:40:01.260
overview of how to build your own editor but not this but then you have to do your own homework afterwards and study
00:40:06.359
glimmer and also build your editor piece by piece in very small increments in
00:40:11.700
order to simplify the problem for yourself and successfully build your own editor so uh tabs what are tabs
00:40:18.720
uh basically tabs uh always fold under something called a tab folder
00:40:26.180
so you need a tab folder in order to have tabs in an application so here I have a tab
00:40:31.859
holder um and if I were to look at where it was assigned
00:40:38.460
oops there you go this is where it was
00:40:44.819
assigned and built I built it using this method and this method is basically
00:40:52.440
the nice thing about being in Ruby not HTML is an HTML if you want to break small parts of the page into their own
00:40:58.980
page you can't without using a technology like rails with ERD and partials on your own account you don't
00:41:05.760
have support for that uh actually the newer the newest HTML is beginning to add support for building
00:41:11.880
HTML components in JavaScript but still within HTML you can actually JavaScript
00:41:17.520
for it here the nice thing in rubies I can just build their method that has a useful name and then call that method
00:41:23.520
and within it um I can just build a tab folder and add listeners to it drag and drop support
00:41:31.020
Etc like because you can also drag a tab if you want to split the screen sometimes like
00:41:38.940
yep so I can drag and top
00:41:44.880
close now okay okay there you go so I can also split the screen by dragging the top
00:41:58.339
tabs always start with a tab folder and then I usually uh when you first open
00:42:04.800
the project it might have no tabs like zero tabs and then I add tabs to it by
00:42:10.920
reopening the content at the tab so that's why uh I end up relying on this dot content method that I showed you
00:42:17.400
that lets you reopen the content of the tab folder and then add top I tap items and then within the
00:42:24.180
top item I add the text editor widget so this text editor component that I showed
00:42:30.540
you earlier uh which has the class name text editor by convention in glimmer if you build
00:42:36.420
like a class name with this class name and it includes the custom widget mixing
00:42:42.900
that will automatically make a text underscore editor uh low level sorry
00:42:48.420
under under case methods available for you within the glimmer GUI DSL to invoke
00:42:55.079
that component and this is what this is how I can use here the text editor keyword so that means you can build an
00:43:02.099
unlimited number of Widgets or components for yourself for your GUI apps in order to build higher and higher
00:43:08.819
abstractions that simply find the way you think of your app in fact like I mentioned in the
00:43:15.180
beginning Gladiator itself is a keyword of those so that means I can include this entirety of gladiator in in another
00:43:22.380
glimmer app by using the keyword Gladiator only that's it does everything that we're seeing here just with one
00:43:28.140
keyword and that that comes from lisp by the way this is one of the great things of why Ruby uh inherits some features from this
00:43:35.460
like embedded dsls okay tabs find and replace
00:43:40.859
um I think it's simple enough I don't have to cover it keyboard shortcuts keyboard shortcuts are basically another
00:43:46.079
form of controller um on-text editor for example at the bottom I have a listener that
00:43:53.339
says uh uh on verified key verify key listener will execute wiper right when I
00:43:59.640
hit the key but before the key shows up on the screen so if I'm typing for example then I'm typing a character uh
00:44:07.440
right when I hit the character but before it shows up on the screen the verify unverify keyless mirror will fire
00:44:13.160
and here within this listener I just checked what that key is and then I execute a shortcut so it could be uh let
00:44:20.099
me show you some shortcuts so I can do this like indent in and then out that's Command right and left uh
00:44:28.140
the brackets I can do commands up and down I like that one a lot like it lets
00:44:33.180
me move a line up and down very quickly I mean I used to use this in Eclipse a lot the eclipse has that feature so I
00:44:39.420
kind of wanted to add it to my own editor um I can duplicate a line command d
00:44:45.240
uh so all of those are just here like see I check okay the user click command and click Z so that must be undo and
00:44:52.980
again like see if I do uh do this I can hit command Z and that would undo it
00:44:58.740
so that's undue so all the shortcuts are basically I check for every key
00:45:04.260
combination and whatever that key combination is I end up uh firing the command for it oh yeah that's
00:45:11.280
one thing I could cover now undo redo support is done using the command pattern that's the classical way of
00:45:16.980
implementing under redo usually um so that's why here I for every command
00:45:22.920
that executes like this this will fire a command that will say indent and indent
00:45:28.440
in uh how now I have a command class that will actually keep track of the
00:45:34.740
history of all the commands that fired and that's how I go on to redo eventually I can fire an undo command
00:45:40.140
it'll just go back to the history of all the commands that ran and it'll uh go back a command and basically undo the
00:45:47.460
text content value to its older version so I keep an old a version of the text
00:45:54.180
content content on every command change um one one more thing I had to do is um
00:46:02.520
usually commands are except you have for every command like indent or Al Dente would have a separate class
00:46:08.520
that would be the clean way of implementing command pattern uh the thing is when I started building the project I had a file uh class that was
00:46:16.380
representing all file operations on an open file and within it it has methods
00:46:23.160
for every operation so I have a method that says uh let me show you um
00:46:29.300
uh prefix new line remove line insert new line indent I just showed you and
00:46:35.819
then outdent Etc I have a method for every one of those operations I could have refactored this code and
00:46:42.720
moved every method into its own command but I was trying to save myself time and I used some of the crazy Ruby meta
00:46:48.599
programming features to get around that and Ruby every method is a considered um
00:46:54.660
uh a Lambda you can convert any method to a Lambda and then a Lambda is an object so that's already kind of a like
00:47:02.040
a command so then what I did is I used the adapter pattern and I made the command class adapt methods from the
00:47:08.160
file class into commands without making them clearance so that's one just one of
00:47:14.099
the cool things about ruby of course I'd like to in the future to refactor a file and break all of those into their own
00:47:21.059
classes that would be a lot cleaner because this is right now a giant file it's like what 700 lines of code so it
00:47:27.240
would be cleaner for me to do real command pattern but I'm just showing you Ruby is cool sometimes you can let you
00:47:32.819
cheat and get away without having to do that the pattern right away but in a future
00:47:38.640
refactor interval hopefully I'll handle that you guys saw split pain you saw menus
00:47:43.980
um you saw how to run Ruby I mean running Ruby code is the simplest feature all you have to do is uh read
00:47:49.319
the contents of the file and eval that's it in fact I bet I have an email here no
00:47:55.440
Maybe I'm Wrong maybe I did it how did I do it
00:48:04.380
maybe I did it a different way let me see do I have her one here it would have to be here okay there it is
00:48:10.680
run oh never mind I didn't do eBay see I I whites clean coat I don't use Eva I'm
00:48:16.380
just joking because Eva has a very bad reputation in Ruby I used loads instead of Eva which is the the next ugliest
00:48:22.440
thing but I'm just joking it's not that ugly in this case because if you have a file and
00:48:27.540
you want to run it as a ruby file like and I'm already inside Ruby you can just load it so that's what I do I just fold
00:48:34.859
it um and oh yeah if it's a scratch file it means it's a temporary file I will I
00:48:40.200
created in a temporary place and then I load it pretty much it
00:48:45.300
so yeah I think I pretty much covered everything here these are three esoteric
00:48:50.940
features towards the end that I'm going to cover basically watch the file directory yeah watch the external file
00:48:56.880
directory system for changes that means that if from outside of gladiator I end
00:49:02.339
up creating a file it will show up over here so let's hope it works right now
00:49:09.599
sometimes that doesn't work but there it is okay so if I were to touch
00:49:17.460
it's just a new file and then I think it showed up yeah there
00:49:23.339
it is new file so this is basically I have a I'm using a gem called a file Watcher
00:49:29.700
that will watch the file system for changes and as long as soon as they happen it will reflect them in the in
00:49:34.800
the in the Tweet the files Explorer retrievement Gladiator so that's what that is now I'm just using a gem for it
00:49:41.640
I bet it's the code is here file launcher I stop it when I close the file but I
00:49:49.020
you know dude it's it's just uh FW oh yeah and I have to do it in a separate thread because you don't want to pause
00:49:55.380
so given that I'm using Jr Ruby not C Ruby I have support for multiple threads
00:50:00.720
at the same time I can run threads in parallel which is not possible and c will be unless you use the new feature
00:50:06.240
which is called Raptors uh but fortunately in Jr Ruby I don't have to worry about that I mean I personally love threads because I used to be a Java
00:50:12.660
developer in the past and Java has threats and why not use them so this is a good example of using the myspina
00:50:19.200
separate thread to avoid pausing the IDE which is pretty much fluid and fast uh
00:50:24.660
but in the background they're basically demonitoring the file system for changes
00:50:30.059
um remember last open files you guys already saw that in action when I opened
00:50:35.339
Gladiator opened all the last open files that's just a matter of me uh uh
00:50:41.819
basically I have a load load config ignore path load config
00:50:50.280
that's a load config I store a yaml file somewhere that contains all the last of
00:50:55.980
all the tabs that are open um and when I close Gladiator and open it again it just looks at the names of
00:51:02.640
all the open files from that yaml file and it opens up all those tabs that's it
00:51:08.520
um database never exactly and finally ignoring irrelevant paths so if I have
00:51:15.839
uh uh like logs I don't want to open log logs are usually files that are like
00:51:22.440
have 20 more 20 000 lines of code or sorry lines of data or more like logs in
00:51:28.380
a web application uh so I have a bunch of directories that are ignored by Gladiator by default and they're all
00:51:35.160
over here uh yeah like basically dot get directory ignorant coverage
00:51:42.140
which gets data when you're running tests packages node modules nobody wants
00:51:48.359
no numbers temp vendor package disk test reports so these are
00:51:55.260
all directories that are basically ignored and you can add more files through them and basically whenever you create a project a
00:52:02.099
gladiator will generate a DOT Gladiator file that's hidden that maintains what
00:52:07.680
are all the open tabs this is so this is the animal filed and then I told you about I'm showing you the content of it
00:52:13.440
these are the open tabs uh this I even maintained what the top pixel was like
00:52:18.720
how scrolled you were in the file um how many split pains do you have did you
00:52:25.559
have uh what are the ignored paths Etc um so that's pretty much it
00:52:33.720
so I pretty much covered everything what are some future features that are missing right now if I'm Gladiator which
00:52:39.119
I haven't gotten into uh the first one for sure is file content search right now I use grab for now so
00:52:48.300
so yeah file content search uh there's a command on Linux or Mac called rep you
00:52:54.720
can use it to look up the contents of files I use that for now and it works pretty well for me for my you know
00:53:00.780
workflow but I'd love to in the future support doing file searches within Gladiator directly that would save me a
00:53:07.740
bit of time um themes right now there is internal support for themes
00:53:13.140
I'm sorry about misspelling here but uh that is not exposed so I can actually
00:53:19.260
aim it but I personally selected the theme that it has right now
00:53:24.359
um Mark Andre recently asked me if it supports dark themes I said no but I could quickly support it if needed so
00:53:31.079
I'd like to support that in the future extensions you can right now extend it it's open source you can just take the
00:53:36.839
source code and extend it that way but I'd like to make a more
00:53:42.599
by you know providing a pattern for people to follow in order to build extensions um and maybe others can contribute that
00:53:49.260
to the project they could just build into some medical requests and I I'd be happy to accept it and finally better
00:53:55.020
Windows and Linux support I mean I'll mostly use it on the Mac on the Mac I like it I think it's very good for my
00:54:00.240
needs but on Windows or Linux could use a bit of improvement uh and that's about it that takes us to
00:54:06.359
the Q a phase
00:54:17.099
move on to something else to announcements
00:54:22.700
how syntax highlighting works like um let me get into that yes yes I meant to
00:54:28.500
get into that so syntax highlighting is part of the
00:54:33.540
code text custom widget which used to be part of gladiator but then I thought it was so useful that I extracted it from
00:54:40.260
Gladiator and I moved it to glimmer so now any user of glimmer gets access to syntax highlighting for
00:54:46.380
for free for all the languages that I mentioned are supported uh and by the way the languages that are supported are
00:54:51.780
mentioned here in the readme they're uh these are these C C plus plus Crystal CSS jsx JSP Json JavaScript again we
00:55:01.319
pretty much all the things that you care about SAS CSS SQL Etc
00:55:07.140
um so this is the code text custom widget which is not part of glimmer
00:55:13.200
um the key part about uh the key logic regarding syntax highlighting is
00:55:20.520
is me not doing the syntax highlighting uh myself basically what I did is I used
00:55:25.619
a library called Rouge which is a ruby gem that has support for over 100 programming languages so I'm exposing
00:55:32.520
about over 30 languages that I care about in Gladiator but the library itself supports me a hundred languages
00:55:37.920
even more than what I'm exposing and Rouge does the support by I'll show you how it does it all you have to do is
00:55:46.980
there it is online get Style this is a listener that will get triggered on every line you see on the
00:55:53.940
screen in this widget before it gets rendered so this screen that I'm scrolling right now on every scroll or
00:56:01.079
change that method will get triggered and it'll basically go in and ask you to provide
00:56:06.839
it with style for every character that's showing up here so what I do is I use the Russian by calling I abstracted the
00:56:14.400
whole ruchamp thing using this method called syntax highlighting so let's dig into what syntax highlighting does
00:56:21.299
there it is it basically uses the Rouge Luxor and it lacks is the text meaning
00:56:28.140
it breaks it into tokens and for every token uh it'll give me so this is doing
00:56:33.420
all the work this is doing almost everything for for every token you see here like this for example this line
00:56:39.900
here is light blue this is dark blue this is uh red for the colons each one of those is a token uh
00:56:47.400
for every token it gives me a foreground color background color font style uh
00:56:52.680
everything I need to know in order to style it so that I return this data as part of this method I actually pass it
00:56:58.079
to sorry I pass it to a block here oh yeah I I basically iterate over each token
00:57:05.700
and for every token I grab the token index the token text the type the
00:57:10.859
foreground color background color bold italic Etc and I and I convert adapt all
00:57:16.920
this data that's kind of like the adapter pattern into what swt wants from me swt wants them in a style range
00:57:24.359
object and that represents a token so then I give the Salvage object everything it needs over here you know
00:57:30.240
what my foreground color background color size Etc um font font style as well and uh
00:57:37.680
I dump it into this thing and then I pass this thing into the event object that comes to me from here and when the
00:57:44.400
method when this block returns um The Styling happens using the swt style text widget
00:57:52.859
that's it so this is all abstracted into a code text component so you don't have
00:57:58.140
to worry about it just give it the language it just S2 for the language and if you want the theme as well so there
00:58:04.020
is support for theming uh and whether you want to see lines or not this is what I showed you in this example I can
00:58:10.020
see lines if I want or I can show highlights of code without mines oh no this one does have life but Without
00:58:15.359
Borders this one doesn't have lines each one is a different theme
00:58:20.640
so yeah that's it any other questions
00:58:29.280
yeah what was the most difficult thing or the thing you're most proud of in the project
00:58:34.680
nice
00:58:41.099
I would definitely say syntax highlighting was one of the most worrisome things that I thought
00:58:47.640
about at first that I was worried I wasn't going to be too difficult for me to Independent
00:58:54.000
um but like I said my only other editor was Bim and so tired of using them
00:58:59.940
so I I hunkered down and implemented syntax highlighting successfully and I was so happy when it worked the first
00:59:05.460
time being very sad
00:59:11.520
here's the deal man Vim is like is like having a Chevy okay a Chevy Malibu
00:59:16.920
uh I'm trying to build up Mercedes you may have a Chevy Malibu modified add a
00:59:22.559
whole bunch of cool features to it and extensions but it's still a Chevy Malibu it's time percent extended I understand
00:59:27.960
the same technology as the Eclipse which is used for NASA for NASA so it's that's like the Mercedes of building code
00:59:35.220
editors so that's what I'm trying to aim at not Bim I have a question though yeah sure if
00:59:41.099
it's about them oh yeah if you were to if you wanted to replicate web user experience I mean
00:59:47.520
like the ulti Combos and stuff like that how confident you think you could do that like with your DSL and stuff like
00:59:53.099
that um no you can Implement anything against yourself for sure man
01:00:00.720
cool uh the reason I say it can you can
01:00:06.540
Implement everything is because even if there was a wizard that didn't and swt that you dreamed of somehow like
01:00:12.839
say a film editor um you can build your own widget from
01:00:18.240
scratch if you want using custom graphic canvas graphics so there it does have support for canvas
01:00:24.359
Graphics meaning pixel by pixel if you want you can build your own widget but in general you probably want to avoid
01:00:30.660
that unless freely maintenance because that's for work but it's possible if needed do you feel confident that with
01:00:35.700
all the patterns you have right now you could like get something close to what Vim has pretty fast since like it seems
01:00:43.020
like the app sections are really good or do you think I I would basically try to
01:00:48.319
rewrite vim and Ruby no no I'm pretty sure you can do it
01:00:54.480
I wouldn't do it myself because I'm pretty sure you can do it
01:01:01.200
any other questions okay thank you everybody oh yeah one
01:01:06.420
more out how would you go about uh let's say I had an idea for a certain extension for them for Gladiator how
01:01:13.260
would you go about it if I wanted to go and create it because I understand that I could go forward but I don't think
01:01:18.780
it's really uh uh negative
01:01:24.119
very very efficient to add to add features you're using with with Force
01:01:29.460
for every everything I want I would want to add maybe uh if there was a DSL or
01:01:35.339
extensions it would be a little easier yeah I mean you could get started by
01:01:41.040
implementing support for software extensions I'm sure that work for that is useful
01:01:46.260
however I mean to talk about a bit about that I used to use in Java as something
01:01:51.900
called the eclipse Rich client platform which is the eclipse itself is built on top of a framework and I I've used it to
01:01:59.460
build desktop but sorry yeah desktop applications for business in a previous job and the way we used it was we built
01:02:05.280
extensions for for the eclipse sorry not that not the ID the platform itself the
01:02:11.099
eclipse platform um the rich client platform so um things that they made extensible for
01:02:17.640
example is when I open a tab right now I support say 100 languages programming
01:02:22.740
languages if somebody wants to add more support for something Beyond Rouge with the Russian provides which is to be
01:02:29.700
honest it almost covers everything but still if you want to invent your own language
01:02:35.160
support for it I guess we'd have to open that up so I'd have to modify the code text thing
01:02:43.559
widget to support passing a block that will do all the work that I just showed
01:02:49.859
you about the lecture how Lexus lexor Lexis the world the words the tokens and
01:02:55.619
then it gives you uh coloring for them so that's one thing uh another thing would be every part that you see here
01:03:02.579
like the file explorer the file lookup the navigation area is a component if we
01:03:08.220
can also support the idea of people building their own components as extensions that can show up anywhere on
01:03:14.339
the screen that's something that Eclipse used to use and supports with their framework that would be cool to support
01:03:21.000
with this in Ruby so yeah there are quite a few ideas on
01:03:26.280
how to extend this also you can extend the menus and you can extend um the
01:03:31.859
right click menus and you can extend preferences if needed
01:03:39.359
but if you ever think of building one of those features please submit a pull request over
01:03:49.980
cool uh well that concludes the Q a phase let's get into that announcements thank
01:03:56.460
you