Neovim Lua Plugin From Scratch

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
uh okay so wait i didn't even say happy birthday to you yet happy birthday by the way bash about to get banned from her own channel hey if you guys don't say happy birthday you're banned from the channel and then she just forgets all right so time to we'll get a little more serious now so uh today we're gonna create a lua plug-in from scratch so from nothing i have an idea already of the plug-in that we can make since that part uh everyone has to come up with on their own i can't help them with that part but i figured it would be pretty fun and bash i um when we were chatting about it you know we were just talking about how hopefully we can basically just like stop along the way at all the different points because it's one of those things where like i've literally i wrote my first lua plugin in probably like 2016 for any of him so so it's like i just have forgotten that people are like what do you mean you need to put it in this folder like that's not a thing that i like remember so so bash is going to stop me every step weird flex yeah thanks um bash is going to stop me along the way chat feel free to ask questions of course too and we'll just see kind of how far we can get on writing uh somewhat straightforward like in concept plugin and we'll try and do some stuff like um i can try and show how we like test a plug-in how do we like get vim help docs like all those kind of weird things that are very much like neovim specific that are hard to find on the internet we can try and try and do that today any other comments bash um no cool all right that's great okay um what is a plug-in that's a great question so we'll we'll start off with explaining what even our plug-ins because it is actually kind of interesting when um people i think you'll see like coming up shortly that if you understand the basics of like configuring neovim for yourself you're like most of the way to making a plug-in already so like bash just like how we did the stuff for configuring telescope or whatever like writing functions to manage your lsp config if you can do those that's like 90 of the way towards writing like a simple uh lua plugin for knee of him which is cool so like yeah go ahead [Laughter] uh no that's totally fine so so basically one of the cool things that i like a lot about sort of the way that neovim does this right is that basically like configuration and scripting and like a bunch of a bunch of other um like all of those things are all sort of the same concept so if you can write code for your config you can write code for a plug-in which is cool i think it's uh really nice so as you get better at each of those then it's good so i think my audio is messed up okay keep talking okay you're good do you want to switch maybe it's just which mic you have or something i don't know yeah that's what i need to do okay that's fine all right well while bash is doing that we'll do the really boring boring stuff parts first i just figured we'd show everything so literally the easiest way to start making plugin make a new repo on github or equivalent you know you can use git lab or anything you like this one's going to be called stack um stack map dot nvim because it's kind of like a stack of maps do you want we'll do no underscore just stack maps stacks of maps um you know how to read me you know get ignored lua is fine doesn't really matter choose a license i usually go with mit because i'm not worried about people stealing my stuff that's fine basher's your mic just not plugged in it was just not plugged in oh my goodness you sound so much better now he was using my webcam [Music] stream onto yours all right i'm expanding the stuff stream that's literally like hey tj um why is bash sound so bad like can you just fix your discord it's probably your audio okay thanks thanks okay so that's totally fine that's totally fine uh okay so we've uh we've created a repository that's uh that's good that's pretty easy that's the first step and so then now what you can do uh there's a couple pretty interesting things depending on your plugin manager i like to have uh i just have a folder on my machine for all my plugins that i am actively developing surprise there's a lot of them you could just put this anywhere though on your computer right so you can just clone uh the repo that you just made right so you've got some get repo here and now we've got uh basically just a blank slate of a repo okay so the uh if you start writing code in here it will not yet be accessible to neovim because you don't like neovim doesn't know what this thing is right it doesn't know how to connect this so most uh plugin managers you use uh plug but this this should work basically similar for anybody else as well is you can just specify like a path on your file system to load this plug-in so that's like the first thing you need to do so that neovim can sort of figure out uh that you want to run code from this place does that make sense yeah so sorry which what file is that this is this is my like this is basically like my anita lua of plugins for packer so i've got packer which uses like this use thing and i should be able to do something just like home tj devries plugins um we just call this stackmap.nb okay so if i do this this basically says import it like a normal plug-in yeah but just use your like relative path or you're like you're sorry not your relative path your like full file path to your local yeah so for you it'd be like plug home bash plug-ins you know stack map now if you're just sort of consuming this plug-in as a user then you wouldn't do this obviously you just let your plug-in manager manage it just like normal right but one of the sort of interesting things about the way the neovim like plug-in landscape is right now is it's basically just git repos right so it's like okay well i have a git repo i just need to tell my plugin manager where is that git repo does that make sense yeah cool yeah so if i do this and i do um packersync to just basically install my stuff you would do like plug installer plug update or something like that now we've now we've got it installed okay so that's that's what's going on there so there's now that this is installed we would be able to find code in this place okay so if you remember from how we set up stuff in our in like your init vim and sort of your config and vim right we just put something in a lua file and then we can basically require that lua file from somewhere else right so we put it in a lua folder so let's just make a folder lua every um place in the vim run time and when i say vim runtime do you know what i mean when i'm saying vim run time bash um yeah so would it it would it be like that would that be the runtime yeah so the rundown is basically just like a list of a bunch of different folders that neovim considers like um sort of like base folders and then in those base folders it looks for different important folders for example it will look for lua a folder named lua there and it will add those lua folders to sort of like the the lua environment oh yep so and then also um i don't remember if we did this in your config but if we did something like make dur plugin so plugin's an important folder and we'll come back to that in a second we configure something in here and i'll just call this one example it doesn't really matter and this just says print um hello bash if i open up neovim again we'll see that that actually prints so there's a couple important folders for your both for your plugin and for your regular config they work exactly the same so if you have a folder in your neovim config called plugin and you put lua files inside there they will be automatically executed on startup okay um i think this is probably defined somewhere in hell plug-in maybe right plug-in i'm not sure if the exact help file oh of course it's embarrassing i'm sorry i didn't know the exact help reference for once off the top of my head is it even the real teach yeah i think we should prob let's just pack it up yeah i'm getting old now i'm forgetting so so basically though if you put something uh in plug in plug-in for your init or you put it here they'll just automatically get executed which is important if you have some things in your plug-in that need to get set up at the very beginning of neovim startup time right so neovim goes through and basically before you get to um you know you run neovim press enter stuff happens and then eventually you get to this like welcome screen all the stuff in plug-in gets executed before we get to this plug-in screen does that make sense you following me no okay so all all the stuff in any yeah go ahead ask a question so basically in this okay so basically in a given like we've created a git repo um that's obviously our like our our plug-in name and then in that in that repo we need to have a folder that's called plug-in and that's going to contain lua files and those lua files are going to be like executed at runtime before you can call like anything to do with the plugin so that you can call things to do with the plugin exactly right so if you want to do something like set up default mappings for example we probably won't have any mappings really in this plugin even but it's going to use mappings like you could do that inside of these plugin files and they will automatically get set up or if you needed to create commands or whatever they can get automatically done at the beginning of neovim's uh like execution right so i'll give i'll give an example like if we did something like vim.keymap.set right just like how we were doing inside of your lsp config right so we could do like normal asdf and this could say something like echo hello like this right yeah if i put this here this is inside of my plugin example file and i just open up neovim again i can say nmap asdf to show what's going on and look that that mapping that we made is already there so the plugin file gets executed before we sort of like make it to this and by the time we are inside of neovim then we've already been we've already executed those plug-in files gotcha okay that makes sense that makes sense this is otherwise like how would you be able to call any of the functions or anything like that in the lua file so that's the that's the first folder that's important for making plugins the second folder is if we have a folder called lua right and so last time when we were doing like telescope stuff we or like osp things we made a lua slash bashbunny.lua file right in that file to actually execute it you needed to say require bash bunny remember how we so so code that gets put into the lua folder does not get automatically executed but it is available to the user okay so if we go lua stackmap.lua right so we're gonna make some new file called stack map and let's say print okay we loaded stack map now right so this if we open up a new um if we open up a new neovim here right and we just go here we're not going to see hello stack map loaded right um even though we have this in our file but if we did something like lua require stack map now we're gonna see that message okay so that only gets loaded when someone requires that file requires kind of like import in other languages or like loading up a package or anything like that right so that that happens when you call the function require not ahead of time right so with that would that mean that like basically anything where it's kind of like a more optional functionality where you might not want to load it up right away because it might slow down make the plug-in very slow you want to put it in like the lua folder instead of the plug-in folder yeah exactly and just um if your plugin like our plugin probably won't really have very many things maybe even nothing that needs to get loaded um at the very beginning we'll probably do one thing which is like make a command to sort of like debug our plugin so people can print debug information we can make that command in our plugin section but we don't need to do anything else automatically we can wait until the user requires stack map before we need to execute any code does that make sense yeah yeah that makes sense um how familiar are you with um sort of how lua modules are structured not familiar cool that's awesome so lua basically um works with uh requiring other files by typing require and then you give it some path so we did stack map right and what this actually does is it searches a bunch of basically folders on your computer right and it looks for some file file that either matches stackmap.lua right so it just needs to have basically this exact name here so stack my blue mask stack my lua or alternatively if we did something like this let's um inside of our little folder let's make a folder called stack map okay and if we move uh stackmap.lua into here and we call it init.lua and nit.lua our special our special function or is a special like file type similar to how like python has underscore underscore init i don't know if you've ever seen that or um trying to think of another python's one is actually like super super similar right so if we move that same file into here you'll notice that we no longer have our stackmap.lua we have lua stackmap and init.lua right so then when we go to requirestockmap it'll call in it exactly so now if we quit out of this other knee of him and we start it again and we type require stack map see it's still called that that function right but it didn't it didn't do that so those are sort of the two places that require is going to look but require also understand sort of this concept of like nested directories okay um so if we did something like lua stack map helper.lua or something or let's call it like lua stackmap util.lua because it's classic to have a util function right and we say this is the utils uh file right something like this right so now if we do lua require stack map and then you put a dot to basically that when you put dot that's basically like saying look inside of a folder called this and i call this util now it'll say this is the utils file okay so you can nest sort of like name space them right by putting them in folders deeper with each other and every time you want to go deeper into the folders you just put a dot basically and that will allow you to do that okay okay an important thing for you to know though so notice how the first time we called require it printed something for that right now watch what happens the second time that we require it it didn't print why might it have not printed well because we i guess we already called it it already executed it's already loaded exactly right so require is in general and we have some ways to get around this that'll show on this stream so that you don't always have to restart neovim every time you want to like re-execute a file but require loads a file once and then basically it puts it inside of this table not super important for you to understand exactly the details of the implementation of that but suffice to say basically just looks up in a table to say hey do we have any like anything that's ever been required with this string before oh we do well here you go so actually lua files you can basically think of a lua file actually looking something more like um oh and for some reason i cut off the top of my screen let me try this one sorry there we go um uh basically you can think of a lua file as something like it it's one big function right that gets called like this okay and so whatever this function returns that's what gets stored in that sort of global package table okay that keeps track of what's been done so what we can do is we can actually say instead of um this is like a comment so it's not actually doing it's not actually exactly this was very similar if we did something like return five okay for some reason this this thing returns five now if we quit out of here and we lua print uh require stackmap.util it's going to print this is the utils file because it went through and executed this right so it's executing top to bottom of the file because this is a scripting language right it executes this then it gets to return five and so it returned five and we printed that from our call if we run that same line of code again it didn't print this part right it only printed what the return value was because that's what the module basically like is holding does that make sense yeah yeah so is it almost basically it's like when you require it's like it's running it for the first time and then it kind of caches the return exactly right yes exactly right and so that um that behavior we can use to basically make um but it's basically like it's really important that you understand that because if you keep on sort of just calling like require util and instead we change this to seven right so i've changed it from five to seven on first sort of like pass when you're first doing this what you think would happen now is this would print seven right because what you're thinking in your head exactly right right so it's gonna be five bash everyone give bash a clap in the chat right now cause she got that one got it right first try actual first try so the important thing to remember is that require the semantics of require right is that what it does is it loads a file once and saves the result of that thing this is uh actually not that weird it's like exactly what you would have happen in like python or something like that because it doesn't re-execute the file every time you're importing it right that would be crazy and it would be really expensive you don't want to do that you only want to parse and execute those things once and then you want to use the results of those things from then on so that's that's the first thing um well while we're talking about it i'll just show you it's possible to basically override this and say actually please really load this thing for me again okay and the way you can do that is i'll show you package.loaded this is a global package here oh and sorry this p just means uh print a table out we can go over some of that shortly i'll come back to this but this basically just means print a table package.loaded is one big table and you'll see that i've got a bunch of different things already inside of here because these are all the different packages neovim has already loaded right and so we can just like keep scrolling there's tons of stuff inside of here but if we did package.loaded what do we call stack map dot util we'll see that it is five okay right so the way that you could remove a key in lua is you set a table like this so package.loaded is a table right we're basically getting uh this key if we say equals nil that's sort of like the non value if we say this now this deletes that key from the table okay so if we print stack map.util now it's gonna say no so say hey that tape that thing doesn't exist in this table um and one thing that's maybe different than some other languages as well is lua by default if the key does not exist in the table it just returns nil it's not an error to ask for a key that doesn't exist okay so um so when it will return nil for any anything so if we did stack map util asdf it's still gonna say no it's not an error like oh that was in the table and he said there's no distinction between like was in the table and not or anymore things like that right so now though bash what do you think is going to happen when we do um require stackmap.uto what's the new value gonna be uh seven yeah exactly right because it's just it's gonna print the thing because it's re-executing this file and then it's gonna say seven and then now every time going forward it's just gonna say seven okay so is that that makes sense this is this is an important thing to to get a strong grasp on because what it lets you do is you can keep iterating within the same neovim session you don't need to like always quit neo them load everything up get started you just want to say like oh re-require that thing and let's let's do that again okay yeah yeah that makes sense and then just to like i guess clarify it or anyone who's maybe unsure um so that when you're doing like those packages you're asking for a friend not for you but yeah um just to confirm so when we do uh like lua uh capital p package.loaded where it's showing all of the cached values yep all of those values are basically getting assigned the first time that like in our init.vim or like an it.lua because it would probably be in lua as well um but basically it's like when we've done require yep and then a plug-in it's basically it's grabbing all of the information like all of the what returns from each of those files and caching that with its given key basically like the file name uh in the package.loaded table yeah exactly and um individual like plugins will of course require things and the require is shared for everybody okay so like you know when when you call require telescope in your config and then some other plug-in also calls require telescope they share the same package.loaded like global table uh and it's not running it twice it's just running at the one time and then it's got the cash information and then it uses that okay and this is super important because um let's say you did something like you added um a key to like a table or you basically said like hey telescope i want you to like know this additional feature or i added an entry to a table or whatever you want all of the plugins to share that state right because like if one plugin says oh add you know dot go means it's a go file and dot pi means a pi file and the other file another plugin x hey what file type is this well you want it to like take into account the fact that the other plugin somewhere added like pi and go right does that make sense like you don't want them to be separate from each other which would happen if you were re-executing the file every time yeah cool yeah for sure okay so that's this that's the that's the that this like principle here is not specific to neil them this is just general like lua information this this behavior would be exactly the same if you were running like lua inside of love2d or some other like lua game engine or whatever this is exactly how the lua behaves generally this isn't like neovim's specific uh info up to this point cool yeah all right all right so this is where um now we can start trying to do some neo-vim things oh hey thanks for the birthday wishes judo by the way um let's um i'm gonna show you this pattern is a really common pattern that you'll see in a lot of different plugins and doesn't super make sense until you understand the stuff that we've just done so a lot of times what you'll see is people write local m is something you have a bunch of uh you have local m as a table and you can add stuff to this like example um is like a function right and maybe it just prints high pretty dumb pretty dumb example but that's what it does and then you return m this m this m is uh stands for module so like the way that i like to think about this is i'm creating some new module this module is the stack mac module um and it's inside of this function so now if we uh quit out of these and we do lua uh p uh require stack map you'll see that now we return a table it has one key and the key is example and it's a function cool okay so this is how you get something like when people say require um telescope dot setup and then pass some things inside of here right what happens now is we would do something like m.setup is function maybe it takes in some options and then it's going to do something with these options like this right and we can just delete example so now we can quit out of quit out of here again open it up require stack map and you'll see we have a setup function so this is sort of a classic way this is a classic way that uh we set up plugins in neovim and it's just a convention setup isn't a magical function or anything like that's just a convention that a lot of people have settled on um because it works nicely and and doesn't uh and basically like delays start up until someone actually wants to start it does that make sense cool yeah so let me pitch you the idea of this plugin and then we'll just work on implementing it and then we'll do all the cool little other uh other features okay okay that sounds good so the this plug-in idea came from stream like uh a week and a half ago or something like that and it was basically like wouldn't it be cool if you could do something like um on a certain event i want to make a bunch of key maps basically like make kind of like my own little custom mode maybe like i uh we were doing it for debugging so like when i entered the debugger i would like add a bunch of keyboard shortcuts that would just load up for this situation and then when the debugger thing is done then we delete all of those the problem is of course if you delete those you may not map the key map back to what it was originally oh i see yeah like a temporary state right yeah yeah yeah that makes sense so what we wanted to do was basically be like hey we are programmers we know what stacks are what if we just imagine basically like pushing something onto a stack a bunch of basically pushing a bunch of key max onto a stack we save off all the original ones right and then when we call unmap this later we reapply all the original mappings if available right so obviously like if you didn't have anything mapped to a certain keybind then we would just leave it yeah yeah that sounds cool cool uh yeah and it also lets us just say stacks on stacks on stacks as many times as we need to which is also good yes also good also good also an important part of uh of making a plugin is finding memes to motivate you so uh yeah cool so basically what we need is we're gonna need two major functions we're gonna need something like m dot push right and that's just gonna be some function that takes what i was thinking is probably like a name and mappings and then we'll have m.pop right and it just has a name okay so these are kind of the two functions that i'm thinking of what we're going to basically provide as like a public api okay and what you would do as a user would somewhere you would call something like um i'll just put this inside of some comment stuff like lua require uh map stack dot push and then you'd say like debug mode right and then sometime later you'd say lua require map stack oops map stack dot pop uh debug mode oh and i forgot to put um like this would have some mappings inside here right there'd be something inside of here so we have to decide a couple of things like oh hey how do we um define what mappings look like how do we make this work etc but that's basically the general principle of what i'm thinking yeah this is all good yeah cool so let's think about how would you make a key map in lua bash uh i would use the i think it's uh it's like vim dot i i have to look at it right now that's perfect no no you're good like basically uh yeah it's it's uh yeah oh look at my config there you go vm.keymap.set right so we've got something like that we've got this vim.qmap.set which allows us to set a keymap okay so that's good so this is this is one part of the puzzle so we'll need to know how to use this function so let's say functions we need and i'm going to go through this a little bit slower just so that we make sure that we're grabbing all the pieces so stop me if any of these parts don't make sense so we're going to want to use vim.qmap.set this is going to allow us to create new key maps how to get key maps you might ask that would be a good question to ask right so if if i didn't know what the name was i would use telescope help tags again so this is a really nice way if you've got telescopes you've got telescope this is a really nice way to do this and you can just type get key map and oh my goodness there's some functions to get the key maps amazing ama amazing is this my is this my intro to my lastic tuesday video all over again amazing um so uh and this is so this is just in the help help tags right so you can say get key map this just gets a list of global so that means non-buffer local mapping definitions okay so what when i'm reading when i think this thing oh dang that means i also probably need to keep track of buffer key maps but i don't know if we're going to be able to get far enough to handle buffer key maps or not so for right now let's just stick with global key maps okay okay so we also want and them get key map now the problem is if you just call something like lua and then get key map and you pass in n for normal mode this is an error the reason that this is an error is because these functions are not in the global namespace for neovim in lua they're basically hidden not really hidden super obvious once you know vim.api so there's a vim global oh wow print vim it's some table okay so there's a table vim and it has a bunch of really useful features it's kind of like the standard library for making neovimlua plugins um so then there's also vim.api and you'll see oh hey look all of these nvm underscore functions some of them that may look familiar to us there's a lot of them because we give you a lot of things to work on the uh on knee of him from within it of course right so anytime you see some function that starts with nvim underscore any time that you see nvm underscore the way that you access that inside of lua is vim.api dot the name of that thing so vim.api.envemgitmath is the function that we need to use so if i do vim.api.nvmgitmap and i pass n for normal mode we'll see oh i didn't call uh print we'll say hello hey there's a table there very cool now very cool bash you might be asking tj when you just did that here it just printed this table thing and that's not very helpful because i don't know what's inside the table right how did you do that how did you do that fancy trick from before well bash excellent question thank you no problem i've got a few globals that i um made for myself and some of these will be apparent uh later while we use them but this capital p one is in my mind an invaluable you cannot overstate how nice having this key map is for um inside of your own config when you're writing plugins and trying to figure out what is going on so by default lua prints tables and it just tells you that it is a table and then it gives you the reference to that table gotcha okay right yeah that makes sense that is it is useful because you can tell if two tables are the same right so if i print or if it is exactly the same table as in by reference right not the same contents it tells you if it's the same table by reference so if i just print two random tables this kind of looks like a dude he's like looking at you like never noticed that before anyways [Laughter] you'll notice that these two numbers are not the same right so it's not as if it's a hash of the table or anything like that it represents kind of like its memory address right so it's like this is sort of like the pointer to this table what you want is neovim so once again we have this global vim it ships with a lot of nice libraries we have vim dot inspect and you could do a table like this and say like key equals value oh i don't actually oh yes uh and vim not inspect returns sort of like a string representation of a table so if i do we're seeing for package.loaded that was your exactly yep yeah so when i was doing it before if you just type print package.loaded it'll just tell you hello this is a table it exists at right not not as helpful right so that's why i have this real i have the shorthand capital p that i define in my config and i recommend other plug-in authors to do the same so that as you're uh writing code it it is easy for you to remember this basically will print whatever the value is and then it also returns it as a nice side effect so you could um you could call it sort of inside but that part doesn't super matter the main big part is that it does this print vim dot inspect so this makes it so that when i just type p and i type key equals value it's going to print that table for me in a way that's easy to read um that's important for people to have in their own thing so you can inspect what the results of things look like so for example if we just print them dot api the get key map one it's just gonna tell you that it's a table that is not super helpful when you're wondering what kind of does like the shape of this table look like what does that what does that do right so now we can print this and we'll see that okay it's a list of a bunch of tables each table contains information about a mapping so it has its left hand side it has what mode it's in and then it has this right hand side those are some of the main features that we're gonna need to know to basically remap this thing later right so we kind of need we kind of need to know all of this information to sort of restore a mapping um so yeah so so with those two pieces we can basically start wiring up the very sort of like simple first uh first pass uh iteration of this okay so i think um yeah yeah let's let's let's just uh let's just try and let's just see where we get so the first thing we need to do is we need to decide what this function should look like like what should people pass in to this mappings table what would you like to pass in uh to define new mappings bash so like let's just we can we can make it whatever way we think will be like most useful for users right um so we can pick whatever we want and we can do the work to eventually call vim.keymap.set okay so what like what format would you like to pass things in um as a table to define like a list of mappings like i guess i would want to i would want to put the actual mapping so like for example like leader yeah right so maybe we do something like uh leader uh s or something i don't know yeah do i have that map to something because that would be a good example then uh let's do leader st because i have that mapped to something already okay sure yep and then what do you want to put as like the value for this uh so i guess that would be the like what you'd actually want it to do yeah okay sweet so yeah so this could just be something like uh we can just do our classic um echo hello right because that's easy to see if that's working or not right um oh the only thing that i forgot is we should probably also say the mode that we're doing these in so let's just say that we're gonna map these in normal mode like this right so this would be really cool we could you know you could add other ones like uh what i don't have anything matte to like leader s z so we could do this right so we kind of want to get this situation working right this would be like a really good start of where we can get so let's uh let's try and do that so let's say we get uh mode and we get mappings here like this right so name mode and mappings um so the first thing we need to do is get the key maps right so that we can save those off so how are we going to get the key maps uh just get it from what the you mean like what the input yeah exactly right so we can yeah yep so we can just do this and we can use mode right and so what i like to do is um i'll just basically get to here and then i'll just start like each step along the way i like to start iterating um and seeing how far i'm getting because lua is a really nice like in interpreted scripting language i can just keep on writing like writing code and executing it and see if it's working like i kind of use neovim as its own rebel so what i like to do is i like to do something like i just actually call this function and let's like let's say debug mode n and i actually just copy this here like this and let's just see if i can get this like how far we can go just by running and executing this file okay so okay i have a key map that just executes this file top to bottom um it's for me it's leader leader x because that's like a special one that i use a lot so leader leader x um which basically all it does is it writes the file and then it runs source percent so source is a way to say hey neovim can you run the file that i'm currently in uh or can you run a file and percent represents the file that i'm currently in right so i could literally pass instead like home tj degrees plugins stack maps lua stack maps and it.lua i could literally type this and it will run that or i can just do source percent and it'll do the same thing does that make sense yeah cool so so now we've run this and was like okay cool well we got the maps that we were thinking we were going to be getting before right like there's no there's no surprise there um what we want to do now right is we want to find out if there are any um things in this mappings table right that uh exist inside of this inside of these mappings right so inside of the maps that are currently mapped we're looking for any of the existing ones so we should hopefully find like leader st but we hopefully won't find leader s z right does that make sense so let's just write ourselves a little function that says like local find mapping and maybe it's a function that takes um the maps table right so it's got all the mappings and then it's looking for something with this left-hand side okay so we just want to find something that can do that and then let's um let's just see if we can call uh fine mapping maps and let's just pass in directly um leader st like this okay right so we're just going to ignore the fact that we passed these in with a table right as i'm working through my plug-in i really like to just like run this one step at a time uh and see see where we can get so maybe i'll say like print this and for right now it's going to return nothing because this function does nothing so when i execute this file it's going to say no surprise nothing right or if i said return bash right and i save and i execute this file again it's going to say bash cool okay nice yeah so now what we need to do is we need to find some way to basically look over this mapping table and find out if any of them have this left hand side um do you know how to iterate over tables in lua bash no very cool i i figured that was the case um so in most languages right you just do something like this four x in maps you know and this lua has do and end right you could say print x okay if we do that or maybe you would have like um key value or something like that right and you'd print key and value if we do that it's going to say attempt to call a table value which is a confusing error message given the situation right because you'd be like i don't feel like i did call any table values i don't know what that means so lua's for statement actually requires um they're underneath the hood a little bit more it's basically just like uh it's gonna call something that is like a function here that returns whatever you're putting here so it's kind of like a syntactic sugar for doing something like while true do and next equals uh next or like get next map or something like this right if next equals nil then break and and then do whatever's inside of here okay so that's kind of like what the for loop maps to roughly roughly speaking okay but it's so what we need to do is we actually need to instead of having maps here which is not a function right maps is just a table we need to call pairs now there's two main iterators inside of luan i'll write out these out there's pairs and there's i pairs pairs iterates over iterates uh over every key in a table not uh order not guaranteed okay so you want to do this when a table has like string keys and things like that which is what our table has right because it's got these two keys that are strings if you wanted to iterate over something like it was an array that's where you use i pairs so this um iterates over only numeric keys in a table order is guaranteed okay so this will goes one two three four et cetera okay cool so we don't have anything that's zero one twos before right we have strings over here so we wanna use pairs got it yeah so now if we do this and we run this it's going to print off um a lot of things because i have a lot of mappings right so it's got a bunch of mappings in this table and i can print all these um oh except in this case i forgot uh uh well no there is no zero oscar nice try uh i forgot which maps we were going over we were iterating over the mass return by m.k map which is an array okay that is an array so we actually want to use i pairs yep and we can just basically ignore the first value so it returns index value but we don't need the index we don't really care about what the what what spot in the array it is right we only care about what the actual value is yeah so if we um if we do this now we'll see okay we've got a bunch of tables that's not super helpful so let's just like do something where we print only the first table in the map and it's like oh cool here's the table we would like to find out right if um a left-hand side if if this left-hand side is in this table when you're looking at this table what key might you compare to see if these two things map it's almost like they match yeah so i it's it's cheating because i uh accidentally uh name everything the exact name that it's called inside of neovim which is probably cheating for your first lua plugin yeah you're like uh hey i don't know uh i don't know how to do that oh what's up rob here you go friend oh no i'll feed myself [Music] so what we want to do now would be something like let's do if um value dot left-hand side equals left-hand side right then return value otherwise we're just going to keep going this is basically like a filter like find first or something like that right yeah so now hopefully when we run this um oh right this is so good this is actually a perfect example i was thinking this might happen so um as we're running this it doesn't match for some reason though even though we were kind of hoping like uh it seems like leader st this should map to one of these right but if i do this leader st notice what it actually shows below yeah it actually kind of like i guess like interpolates i don't know what the right thing it changes leader from leader to the actual value that i for my leader which is comma and yes chat i use space as a leader for a lot of other things i just used leader from a long time ago so just relax okay um because i know there's gonna be a lot of people in chat mad so um so instead we could do something like this for right now and i'll show how we can actually calculate what this is later but now if we search with this comma st right which is what it actually looks like as sort of the internal representation we should hopefully get done the information about the mapping that we had set uh that we had set originally right so now we sort of see we have a right-hand side we know what we can map all this other stuff right so now we've found we've found the corresponding mapping uh for our mapping that we'd like to override right so that's good that's step one yeah yeah um now what we need to do right is we're gonna basically uh oh well yeah uh let's i'm gonna change both of these to commas for right now because we'll come back to actually fixing this to figure out later later that's sort of a more complicated question to ask now what we can do is we could say um local existing maps and we'll make our own little table and we want to find all of the maps that are currently existing right okay so how do we iterate over a mappings table with over a mappings table like to find like with the fine mapping or within uh with these mappings which looks like this like how do we get so we need to loop over these right by doing eye pairs or pairs that's the main question for this kind of table for that kind of table it would be with pairs wonderful that you're killing it already yep left hand side right hand side right in pairs mappings like this whoops do and right so now we could say um here and let's do left hand side and let's say searching uh now let's say something like searching for left-hand side and then we'll see if this gives us any useful information right now right so we run this and it says searching for comma sz it didn't find anything that makes sense searching for comma st it did find something so that's exactly what we're hoping to see right that's exactly what we're hoping to see in this scenario is basically that we can we didn't find anything for this one and we did find something for this one so that's really good um the does the general sort of like iteration of basically using neo them as its own rebel kind of makes sense do you see what how i'm doing that is there any anything that i should explain more about what's going on there um i think i think it makes sense i think you explained it quite well as well of just like the difference between i pairs and pairs cool yeah how you're able to iterate over that i think um uh yeah no i i think i'm i'm following cool sweet yeah um all right sweet so now we've found these mappings so this basically means uh local existing right something like this we'll save this as a new variable and now we can do if existing then we're going to do something with this existing value now something sort of tricky that people don't always expect for lua is which things are um what things are truthy versus what things are falsey so in lua the only things that are falsy are false and new okay so false obviously is falsey and nil is falsy but like zero is truthy an empty table is truthy um an empty string is truthy so the rule is very very simple but it's a little bit confusing if you're like main other like scripting language that you write is like python okay right because like in python like it's like falsie uh is like an empty list you're like if not my list then do something and that that works for if the list is empty but that's not how lua lua works so that's just a general note as you're writing your plugins like if you did something like um like local count is zero then you do something and you're looking for the like increment the count and then you do if not count than this this will never work because county is not false or nil okay does that make sense so yeah it's just something as you're like writing lua that's definitely kind of like an edge case that people forget um in this case we're safe because we always only return a table here or uh if at the end if you don't return anything that's the same thing as return nil those are the those are the same like same things okay so uh yeah tc it's a good point lua is great as long as you come in with an unsolid mind yeah lua has a lot of rules that are different from other programming languages but it's pretty consistent internally if that makes sense um like like the rule is simple right for what things are truthy or falsey uh only false and nil the rule's simple uh but that's just not what you're used to like if you write c zero is false like they're actually just the same thing which is that's that's weird and lua gets blamed but so okay so now basically um we've got though uh we've got this existing mapping and what we can do is uh you'll want to do table dot insert existing maps existing so table is sort of like a global that has some different table operations on it the most common one that you'll be doing is just table dot insert first parameter is a a table right and then the next thing is a value that you want to put in right so uh lua tables can contain anything inside of them right it's not like um it's not like uh in rust for example right yeah it's not like it can only be a list of one type like you can put anything you want inside of the the lua tables right uh in this case we are putting in tables into the tables right um so now once we've done this i i'm like okay cool let's make sure that i put the mappings that we expect they are inside of existing maps right so i run this again and sure enough you'll notice we have sort of a table and inside of here is what the the first entry is just a table that has the information about uh our comma st operation good good good so now though now though the problem is we need to basically like save this information between push and pop so how would you like like what are you thinking like what's the next step of ways that we could sort of like save information in between these uh executions well could we have us a stack that's like in our um like i keep calling it cash but yeah yeah yeah you know what i mean right yeah right in the require basically inside the require thing yeah yeah so we could just do m dot understore stack i'm going to put underscore to sort of represent like hey please don't touch this in general that's the that's the soft agreement between like plug-in authors and everything is like hey um if there's an underscore there i'm allowing you to uh use this perhaps and whatever but i reserve the right to break this anytime i feel like it right so it's kind of like uh we're just uh we're saying that it's like a private member of our module right so we could just make stack be a table right because we're just gonna save off this information uh into some table so what i would say right is we have this name and i uh i cheated already because i i knew that i wanted to basically name space these things that we're going to push and pop to our stack of of things although now that i'm thinking about it oh no we do need a name because we need to know what we want to pop later or do oh yeah yeah or do we should it just be one global stack which way do you which way is more interesting uh to implement for you bash which one do you want to do i think we could make it work either way i am i'm i'm kind of indifferent to being on okay let's keep the name then because uh i like getting to write like debug mode here because it kind of at least as a user it feels it feels good it feels good to be yeah yeah i think it'll give you more more options yeah maybe yeah i think so the only weird thing is if you did something like like push debug right and they did push other thing and then you did pop debug and pop other i think this behavior is just undefined uh it could work if these don't have clashing uh mappings right then it'll work just fine but it would be weird because we'll try and like unmap the things that we did here but then so this behavior we'll just we'll just leave as that's potentially kind of undefined uh but that's okay it might generally work just because it's unlikely that you uh clash in the in the two two areas okay cool so yeah so we've got this list of mappings uh that existed from before we were pushing onto the stack so now we need to basically save those so what i would do is i would say m.stack and we could say name so this is how we can access something at this name right equals existing maps okay so now we've basically saved this for between uh executions right so when we require this we'll make one table this table like you said is cached in between other requests and so now we'll have this information sitting around in fact if we do this and we do lua require stack map and then we say p requires stack map we can look at this oh do i have it not showing uh underscore things here let's try uh dot stack did i do something wrong here oh i didn't save this file yet sorry that's why so let's do this so we can even see now right that if we just like look at this table we can see what the current stack is so that's pretty cool we have access to like seeing what's inside of the stack at this time so we're like oh yeah sure i ran this code that pushes debug mode and it's got this stack right here of things there this is the only one that we're going to have to like unmap once we're done okay good yeah cool um now though we need to actually do we need to actually do the mappings right yeah so we can just do the same thing that we're doing before left hand right side in pairs mappings just like we were iterating over before and then we can just say map dot set nice smash left hand side right hand side now we'll probably want to leave ourselves as a do need some way to pass options in here because maybe you want it to be silent or maybe we want it to be an expression or etc all those kind of things um but uh we'll we'll work on that as sort of like a future a future plan let's see if we can get the very first mode uh working first um for people wondering about sub sounds we have the sub sound uh when we're not doing guest mode because today we have bash on as a guest and we don't want sounds to play over when bash talks okay chat nice try i mean if it's a doggo sub music i think it is hearing me talk to be honest i think it's just i mean it's not specifically doggo sub music it's just because it plays when the when the dog comes on i don't think there's anything specifically dog about it so okay then i'm not interested in hearing it yeah oh this is i'm not interested i haven't updated that guest in forever command edit guest bash bunny at bash money at me there we go okay okay so we we've now set these mappings so we can try um we can try like exiting out we'll run this right require stat mac it's gonna put push this on here so now when i do leader s what i'm hoping to see are two new mappings here right and uh i didn't say this file again because i'm silly and we're gonna do that right hand side expected string or function got nil um for iphone dot oh we didn't set the mode we didn't set the mode we got to pass mode here this would have this wouldn't have happened if i had taken the time to read the documentation right did not keep my def set mode left hand side right hand side ops this is a must do though seriously if you're writing your own plugin the documentation for neovim that's built in is really really helpful so take the time to explore it or use like telescope help tags right because you can just search for like key map set and you can find different options all that kind of good stuff um that's that's like how i people think that i like have some magical knowledge of neovim but my magical knowledge of neovim is literally just when i have a question i read the docs that's my magical knowledge that is magical yeah it is i've never done that in my life what are dogs what what are docs well that was like we were helping malkie with something recently and melky and we were like they they were asking melky do you want to know how it works or do you just want it to work and he's like i just want it to work right now yes [Laughter] so so anyways um if you're writing a plug-in i do think though it is good to know how it works and the more you can do that the better you'll understand over time um yeah i'm too good at breaking things to like not understand how they work you know exactly way too much to like not have to make the effort to know how it works so that i can fix it exactly exactly so then for the yep opts is that like optionals or is that like uh should we add buffer is equal to zero yes uh so we um so we don't need to put it um because right now we're not going to do anything with it so if we added buffer like equals zero or buffer equals true then that would only map it for the current buffer that we're in which that is probably something we need to do separately what we probably need to do is add like a push buffer function or something like that later to handle um it basically handle whether we're going to map something like every time we go into a new buffer we sort of need to sketch out how that would work for ourselves for now we're just going to do global ones so the options are in this case optional you don't have to pass them they could do things like turn it to silent or say what buffer it's going to be or whether this returns a string that should be executed there's a bunch of different um kinds of options that we can pass but we don't need any of them in our very first our very first one so now hopefully though if we do this and we require this and we do unmap this we'll see hey here's our two mappings that we wanted to have very cool that's really exciting that is exciting um yeah this is this is a plug-in already you know this is a plug-in it does mappings for you um not very exciting plug-in yet because it doesn't do a lot more than like calling that you that you want to add these maps which is not super hard to do without this but that's a start right so now we've got two options we've got two options bash i can show you how we can use a testing framework for neovim so that we could start writing sort of like um just some expectation kind of tests like you'll run some code and then we can ask like okay do we have these mappings right now or we can do the pop part so i'm kind of leaning towards doing tests but it's up to you yeah the test would be cool okay all right apparently chad agrees they're they're they're ready to see tests okay yeah so that's they're good um what we can do is i've i've got some stuff actually already set up to help uh to to make this a bit uh easier inside of some plug-ins that i that i've already done and we're going to use a test framework that um is written in plenary which is a real word prime didn't believe that it was a real word for a long time he thought i just literally made it up it just means uh full complete entire absolute unqualified this is the definition of plenary because it was i just wrote this because i was tired of writing the same utilities in every single plugin so this is kind of like a lua plugin library right is basically um what it is and i wanted to pick a unique name uh so that people could import and it didn't have name space clashes but prime was just saying plenieri for six months and then eventually planets yeah well yeah it does kind of sound like it does kind of sound like planes as well and i was like prime you know that's a real word right and then he was like no you're lying to me so then he looked it up on stream and he was like oh okay i see that you're not lying to me um so i will paste this link in the chat but we've got a wonderful wonderful testing guide a lot of us written by the wonderful connie um wow connie always coming in clutch it really does really does created telescope um but kanye has done a lot of stuff so this lets us write some cool tests that just look like this we can describe we write some name of the test that we want to do and then we have a function and we can have multiple um they're just called like it each one is it it looks very similar to like jest if you've done that or other things like that and we we can run these and we can just run them inside of uh inside of neovim and what is really quite crazy is these will this file gets executed by a separate neovim instance so each time it's completely new state basically right so it you can like mess with whatever you want and do anything you want and they'll always get executed in different neovim instances um and it communicates over rpc with each other and a bunch of cool stuff like that so oh um so if you have plenary installed right if you have plenary installed all you have to do the telescope what you have it's a requirement for telescopes exactly so obviously everyone already has it installed because we know everyone's using telescope of course right yeah of course um so all we have to do now is uh i like to put these in like uh in just a separate in a separate folder usually um you can put it either in like lua slash tests or you can put it i usually just put it in a new folder tests and so we can do like tests oh go ah did you just have a question wait can you can you make did you just make a folder yeah of course so if you do bang right here you can just run any uh command this is just the command line wow so anything that actually didn't know that anything that you can run from the command line you can just run from right here wow yeah well pretty cool so much you're welcome yeah uh so we could just do sudo rm we're i'm not even gonna i'm not even gonna type it just in case you never know that's too dangerous chat um so yes so this is how i usually make uh i just make like i can make her so i just made tests right so then now i can do but it saves me so much time yeah okay so yeah uh cool pro tip is you can do little known tip by the way uh bang and then you can just run uh commands so that's really cool so uh yeah so now we can do tests and let's just call it uh map stack and it needs to if you're going to use the framework thing that we have for planner the file has to end with underscore spec that's how it like finds the files that it's going to run okay uh and so now we can just write something like describe um map stack function right uh and let's just say it uh can be required this is a dumb this is a dumb test but this will just show that we can do require map stack right i'll write it like this so now that nothing too crazy going on there i have mapped to my leader t this mapping here plug plenary test file this just says hey can you run the test file that i'm currently in so this is a mapping defined by plenary so if you map this in your config it should just work if you're inside of an underscore spec file so if i do leader t done it says oh map stack is not a thing because i think i called it stack map didn't i wait which way did i call it i called it stack map okay cool so that showed you how a test failed and now i can do leader t and now it worked there we go uh magical so it so this uh ran this in a separate uh neovim instance right so this is in a completely different neovim so like the fact that we just ran this it didn't actually affect the mappings of the current neovim that i'm in cool yeah yeah it's awesome so that's um pretty pretty interesting uh okay so now though we can write more tests so we can do something like this let's do can uh push can push uh a single mapping right and let's just do something like this we'll say require stack map uh stack map dot push and then we could say something like test one require yeah um oh did i wait where do we put mode we did here and then we can just put in some yes good call uh we can just put something here so let's just say something like asdfasdf because that's not a mapping that i have and we can just do the classic like echo this is a test okay right so now hopefully hopefully what we can do is after this we should be able to do something like local maps is um vim.api.envem get key map normal mode and what's really interesting right is because this is just getting executed inside of another neo instance you can just use all of the apis that you are used to using right so like if you want to grab all the key maps you can just grab all the key maps right we can do the same uh thing that we did last time which is we can say something like uh for map in ipairs maps do and and what we're hoping to is uh actually let's just make ourselves a little helper function local uh fine map uh maps i don't know left hand side this is gonna be exactly the same as we had in the other place but that's okay it doesn't super matter um if math.lhs equals lhs then return map end okay right so we want to do is we just want to say okay uh local found is find map maps uh asdf sdf right and hopefully this should be something so what we can do is we can say assert.r.same this is built in and you can read about this in this testing guide assert.same probably somewhere in here there's like this whole lua cert library that is just available to you that you can use which is really cool um and so we can just say let's just say that it should be an empty table which is not what we expect right we want it to actually be something when we run this file it'll say hey you passed in this table that doesn't look like an empty table so our test failed which is good that's what we wanted to have happen right um so instead what we could do is we could just say something like echo this is a test found dot right hand side because that's what we expect to see right is that uh we want this whole thing to equal whatever this mapping is that will sort of associate whatever is going on with this mapping to be here and in fact we could say local rhs equals just so that it's more obvious what's happening and we can say this right here does that make sense you following me there i am doing my best yeah so what we're trying to assert in this case is okay we're gonna push a new mapping it's gonna be under the namespace test one it's gonna be normal mode and what we wanna do is we wanna map asdf asdf to this right hand side this right hand side is this string so after we've done that i want to assert that this actually does something right um and so to do that we're going to say okay let's uh look at all the mappings let's see if we have one that's asdf asdf right and then what i want to make sure is that the mapping that has asdf acf is actually this right hand side okay okay yeah yeah yeah that makes sense yeah um so now when we run this we should pass because this actually worked and we could make this fail for example by um instead of just mapping like the right hand side we could say um this will break things okay right so if we just like concatenated this string to the other side went back to here it'll say uh hey what you were expecting was this but you got this will break things which would be unexpected right um so let's uh undo that here so that we don't have that anymore so now when we're in our tests we're here so that's the first part so we could write more tests on this right we could add multiple different things we could add them to different modes it's as simple as just literally writing another it can push multiple mappings right so we'll just do almost exactly the same thing here and we'll just say maybe this should be um asdf1 and like two or something like that right so we should be able to hopefully find asdf1 and then we could just do exactly the same thing here um found one and found two something like this and we can just increment both of these numbers oops uh like this one one one two two two okay so i don't even know if i did all of these at the same time but i think it's pretty close yes okay sweet so now we've just asserted all right it works for one mapping works for multiple by induction it works for infinity uh that's how math works i'm pretty sure right so that seems good something like that there's a few steps in between maybe but i think generally that's pretty much uh that's pretty much where we're at sweet so when once you like have this loop going i find it feels really really good right so you're able to like iterate on some of the basic stuff by just executing the file then once you start doing some more like complicated stuff you can move into like setting up this whole setting up this whole thing that basically like will run tests for you and i have one keystroke that just like runs all the tests okay sweet that's good we can move on right so that sort of like combo there feels really uh fast for me and feels really nice right yeah yeah for sure this is really cool yeah so once we've got this now it's like okay well what would be awesome would be is if we could pop something off of here right and we remove this mapping because that would be pretty sweet so let's try um and we'll probably find that we need to um add a little bit more information i think so we'll we'll see but so what we want to do now right is we want to pop the uh pop the mappings off of that off of the stack so give me give me a general outline what you're thinking you don't have to say the code but just tell me sort of the steps that are going on in your head that you're thinking about or questions you might have about the next step uh okay so this is kind of the the high level idea of what i think we need to do is that basically we need to grab uh get our like i keep calling it like the global cache perfect yeah you can just call it like uh the packages or something like that yeah packages okay yeah so get all the packages and then from there it'll have our stack that's like where our stack uh all the information for our stack is filled so from there we can basically just um remap the if it basically if that if the current uh key map has something in the stack then assign the right like assign the right-hand side that is defined in the stack to the that keybind and then if not then it can just be like assigned to nil and then uh yeah and then you can probably and then just remove that from the stack sweet okay yeah i think that sounds good i think that's basically exactly what we uh what we need to do uh so the first thing like you said right let's grab from our sec so we can say local this is basically like existing right is m stack name right and then we can just clear m stack name equals nil now right so we're just basically removing that from our stack from before yeah um so now we need to basically go through and unmap each of these and um i'm noticing the one other thing that we don't have which is important is we never kept track of all of the mappings that we made initially so we only kept track of the ones that were already existing we didn't actually keep track of mappings oh yeah so i think what we could do instead is we can just push onto our stack existing and then we can put push mappings like this so now we've got a table that's got these two keys okay does that make sense wait where is that is that so this is what we're gonna push we're gonna push on to the stack basically um like when we initialize it uh yes so when we create our new entry in the stack so when we call uh push right we're going to keep track of which things were existing at the time of creating this map as well as well we're also going to keep track of uh whatever mappings we created because we need to know which mappings those are so that we can drop those yeah right so we otherwise would just we just accidentally leave those around which we definitely don't want to do so now we've got these two tables that's uh that's important that's important right so we could just say um we could call this like state or something like that instead right so now state has existing and state has mappings okay um yes okay so that's that's good that's that's where we're at right now so then what we could do is like you said we're going to have to iterate over all of the mapping so that's why we needed this mappings so we could do something right like for left hand side right hand side in pairs uh state dot mappings do end right so now okay so we've got our left hand side we've got our right hand side one thing that i was noticing that's kind of annoying that i didn't think about before was maybe instead of treating existing like it's an array this existing maps maybe we could do existing maps left hand side equals existing so that we can uh just check if this left hand side exists instead of putting it into a table and like searching over the table every time kind of like when you put an element either into a set or like into a dictionary versus a list like in python right this way we are putting it more into like a dictionary and the key is the left-hand side because those are unique uh so we could just not do that part right now we've got existing maps up to this point we hadn't used anything with them so that seems nice um okay so so we should be able to do something like this if um state dot existing left hand side then uh else end right so this is basically like handle mappings that existed handle mappings that didn't exist right those are two cases that we need that we need to uh that we need to do now okay um yeah yeah yeah okay so let's just let's just handle this case first because this case is simpler we're looking to key map and delete something and sure enough there's nvim keymap dell which is just the opposite of nbap nvim keymap set so that's really nice we can just use um the reverse like of set so that's nice because um i'm gonna say mode as well here because we're going to need mode we can just do vim dot key map dot dell we're going to need to do state dot mode and then left hand side right hand side so this should if i remembered everything correctly this should basically just straight up delete any of the mappings that didn't exist before and now we can try and test this right so let's go over to our test okay and let's try and write one it can delete mappings after pop and we can basically just copy very similar to what we did for this one right where we push these now what we want to do is we want to pop them and we should just be able to say pop one what would you want to check after we run this what's the state that you expect things to be after this so you can check that the um you can look for the based on the key yep so based on the name see if that's nil awesome the value yep exactly so what we can do now is we can get the maps again and now that i see there we just do this every single time let's just delete maps from this function and say local maps is bim.api and vim get key map here for normal mode because we're just always doing normal mode right now and we'll just uh delete these right so we can just we can just do this every time and delete this and yeah we'll delete this one as well and then so we can do a local uh after pop oops classic and now we just want to say okay there shouldn't be anything here right because what we expect to have happen afterward after we've deleted everything is that there is no mapping for asdf asdf right because that wasn't a mapping that existed to start with so um when we run this we get a fail dang why did we get a fail expected nil test one normal test one stack map pop okay let's uh let's go back to here uh we did save everything state dot mode left hand side right hand side say that mappings interesting what does our stack look like before we do that so here's a little thing that i do when i'm i'm confused a nice thing you can try so you just do assert r dot same do something that you know is going to be false like require stack map um dot stack i i know that the table should not be empty at this point so what i want to see is what does it actually look like what does the stack that we're sort of keeping track of everything in what does that actually look like at this time and so now i can see okay we've got test one and it should be existing um okay so we do have test one that's good we wanted to have or we want asdf hdf oh okay i think i see the i think i see one of the problems here the problem is that we've um these don't get cleared in between every execution uh because each it gets executed within the same neovim uh but like each file is a separate neovim instance so we need to find some way to basically clear um our stack map fortunately we've got um this thing called before each this is built into the testing framework as well this runs before um every single test yeah um so let's set up yeah exactly so yep yeah yeah so let's just do something like vim.emap.dell um normal mode left hand side is acf acf so we always clear asdf asdf when we start and let's also do uh require stack map dot stack equals just an empty table um in fact maybe what would be better is we should uh we should just do a like something like m.clear here right and this just sets m dot stack equals to this for now maybe it'll have to do other things um but we can we can just call clear here so other people shouldn't call this but it's nice for us to make sure that we've got like sort of this clean slate every single time so hopefully uh unexpected air no such map in b2b to b2b no such mapping no such oh okay let me see here oh only if this exists oh that's uh oh okay so this is airing because this doesn't exist this is a good thing for you to know generally in lua lua doesn't have a concept of something like try do stuff and then accept like this right and then something or like try catch or whatever instead what it has is a function called p call which just means protected call i think at least that's what i always think of when i'm writing it um and what you do is you pass some function in the first argument and then you pass the list of arguments to the rest um if you don't care whether it succeeds or not like in this case i don't actually care um if asdf acf doesn't exist or not right like i can just discard this but in general it returns okay and then basically the rat value here if uh ok is true then it means this function call succeeded if it's false then the error message is passed in the second value so if it succeeds so if this is okay then this is whatever this function would normally return if it errors okay will be false and this rhett will be uh whatever message was causing the error in this case we don't actually care if it errors or not we're just basically saying um please don't have this mapping when we start right we just don't want this in between executions that's all we're saying basically in that case so um cool so this still failed that's all right though oh duh of course i failed we we left in i forgot that we left in the thing that forces it to fail oh right smart remove the thing that forces it to fail actually rocker if you uh there's a really sweet uh plug-in that adds uh plug-ins lua there's there's some like lua docks docs vim doc lua vimdocs this plugin right here uh and i think it's these two docs these two together that add a bunch of uh lua help stuff into knee of himself which is really cool so if i do help pee call it'll actually show me the information from pee call this is originally from the lua 5.1 guide but someone transformed it into a way that you can put it inside of here so you can uh have really nice help stuff inside of uh neoven yeah hashtag there's the neoven plugin for that indeed um okay so now opps expect a table got string d d d d okay so we need to go to uh that's not the one i meant to do biblical f109 so this is nice because it's set so one thing to do as well is to learn how to read the stack trace back for lua so what we see is that in here in this function pop on line 61 we are not passing uh the right stuff to dell so let's hop back over to our thing here we go to line 61. oh we don't have to pass a right-hand side that's not how deleting things works you only need to pass a left-hand side um that is sort of how deletion works right okay so now now hopefully when we run this we get pass sweet so we've successfully done it now for the case where we know there are no existing mappings yeah right so i would say um no existing that's basically just copy this uh function here and then say instead of no existing let's just change this to yes existing um and so what i would do yeah it makes it obvious for me what's happening right so now the only difference in this one should be that we can do vim dot keymap dot set normal mode right um asdf asdf obviously it's the best mapping and we can say um echo og mapping you know something like this so that we know that this is the original mapping yeah um i like it so now this is gonna fail for a variety of reasons uh but primarily because we know for sure that this doesn't do anything because this is basically error not yet implemented right um we we have not done we have not done the actual work for this section yet so if we go to here and we run this test we're gonna see something that it says hey sure enough not yet implemented we haven't done that yet um so let's try and implement also this uh this test isn't right we can just write what the correct condition should be which is that it should be saying echo og mapping here right and this should be um after pop dot rhs all right so what we expect to see is that this pushes on to it echo this is a test it finds it right and then after we pop that off we should still have og mapping now right that's like the whole point of our plugin um eventually right uh and so obviously right now it's still gonna say not yet implemented because we haven't implemented this so so then okay yeah so then if it's existing yep then what we would basically want to do is uh we want to get the plug-in table uh the the like the cache ah yeah okay yeah yeah right so we actually already have that that's right here so this is this is that original mapping so we could say like local og mapping is this right so this is the og mapping table that existed yep and then what do we need to do okay so then basically okay so if that exists uh we want to assign the right-hand side of that yeah from the original one assign that now to the left-hand side yeah so do we need to like set the existing one to nil first or we can just reset we can just like set set a new value to it right um yeah we don't even have to put anything in this existing because we're just discarding this whole table once we're done so we don't have to clean up at all we've already uh dropped it from the stack right and since lewis garbage collected and everything we will just be this table will just be disappearing into the ether since there's no more references uh to the table anymore so just like you're saying we can just do the right hand side now there's a little bit more work that needs to be done here to do handle the options from the table for example if it's an expression mapping or if it's silent or other kinds of like options that you can attach to a mapping we still need to do that but first let's just try and see if we can get it to to do the right thing here right so if we go back to our test boom bash good call that's awesome yeah so now this now this does the very basic version of what we wanted and what's cool is we even have tests that sort of like are validating that it actually like works for real yeah right that's awesome um so that's the that's the main stuff um so now we'll just do quickly how you could write some vim docs for this um just really short it we can do maybe next month maybe we'll implement the like second set of features for this uh pr which would be like buffer mapping and we can make it so that it automatically generates the documentation in ci and we can automatically run the tests in ci and we can add some of those sort of like other features that really um surprise people that it's possible to do with the open plugins right like oh what do you mean i can run tests in ci like yeah we uh we've done that we figured out some of that that stuff to make that work really nice so so let's write just like the most basic bare bones dock let's write a readme and then we'll just push and then we have made our first neovent plugin yeah yeah so that sounds awesome um basically i'll show you an example of uh of like telescope so telescope in a doc folder so we have to do and once again chat you can use bang here to run a command doc um in a doc folder you pretty much just have to put something in here with a name and dot txt all right so we can do doc uh stack map dot txt okay and we can look at telescopes as an example and well telescopes is okay it's fine basically there's a couple things that you need to do you need this magic little thing at the bottom which tells uh vim that this is a help file this is called a mode line by the way help mode line and it's just a way for vim to know that things are a certain file type and other stuff like that um so you can just put this here and then if you uh edit this notice how now my file type is help right down here uh because this says ft equals help if i change this to like fd equals lua and i edited this again it would think now it's a lua file oh so anyways there's uh there's that okay so in general the way that people make this work is um i usually put a nice little a bunch of equal signs in a row mode lines are a good way to compromise your system yes kathy you're true about that don't use them generally but they're good for help files they can't execute anything in this case so um and the way that you get something like this telescope.nvim so that you can type helptelescope.envim right right you need something of a specific format the format's really simple you just put a star and then you say something like stackmap dot envem and a star notice how mine turns to green now after we do that and then you can just type right i think it's just right and then it'll align it to the right that's just a command inside of them you can look at help right it uses your text width to find out where that should be so our text width is 78 so it puts it aligned to the right 78. um you don't need to press space the exact number of times to line that up um right so then we can just do something like um this plugin helps you to set a bunch or to push a bunch of maps on some events and then pop uh them when you're done with that event see examples something like this right below yeah and then what we can do is we can just write very similar to like how telescope has um let's find like a good example in here of just like a normal function just a smaller one actions these these ones are fine so basically in general the way i like to write this is you can do something like this where you'd say stack map dot push and then this would have name mode and uh mappings right because that's that's what we said and then you're gonna wanna make a uh like another tag basically for this so you can make a tag by just doing that same thing stack map dot push right yeah like this and then we can type right there we go so now that's there looking really pretty and then we can just write something like push the name uh mappings for a particular mode um this reminds me to do we should handle different modes separately i don't really know we didn't super think about how to handle like the fact that you could have a name that's like in one mode and not in another i don't know we have to think about that for us later right um yeah mappings should be a key whoops key value pair of uh like left hand side equals right hand side something like this and then uh you can write an example by putting a little bracket going this way and then a bracket going this way they disappear when you're inside of a help file and then inside of here it's kind of highlighted different so you could do stack map dot push example normal um you know something like space you know st echo wow this got mapped right and then dot dot down here so there you go so that's so that's example of stack map dot push uh we like i said we have to think about how we're gonna handle different modes but that's that's our problem not not the user's problem for now so then we just the only other thing that we care about to show people is stackmap.pop we can right align this we can say stackmap.pop and this is just name maybe what we should do instead is we should put mode here this would be super easy for us to implement and maybe we should do this really quick where i just think of this mode um and we do like stat mac like this oh yeah yeah this is super easy to add right and we just do this and then we say instead of putting mode here we put mode here we'll just need to basically um equals this is a really common trick that you might do because only nil or false are falsy right if even if this is an empty table it will return this table first and short circuit and not execute the rest of this so this basically drops in a table by default if this name has never been made before um to do next time show bash about meta tables pog slide okay next time we'll show you meta tables this is another like nice little lua ism it's very similar to like python double underscore methods and stuff like that um you know where you could do like double underscore stir and it changed how like string gets displayed and stuff like that we can basically make this like table always return a table instead of nil so that okay when you access it you don't have to remember to do this you can always just access it like this way does that make sense this is it's just a it makes it easier to use and do some other stuff like that and say yes to meta tables rocker nice try um so now we do this this this um state state dot mode we have the mode and we have the mode here so i'll just clean those up just to make sure state mode mode name mode bbb i think that's good um oh yeah and we don't actually need the right hand side because we're not going to use that we can go over to our test and run this this is going to say hey this doesn't work you didn't pass mode to these right for our pop ones here we now have to say normal and i think normal here right and all our tests pass again wonderful so we can now go back to our doc file and then we can say mode this is good here so now we can delete that um pop the name mappings i notice that i said mappings okay mappings um uh restores original mappings uh from before calling stack uh stack map dot push so one thing that's really cool is uh when you use these little these little pipes here and you put something inside of here now if you're inside of here and you press capital k um or it should be that oh because i'm in i'm in some weird file that oh i know why that is okay uh if we do this after closing and opening again because i didn't do this i think this will probably work but uh oh i gotta do this sorry doc i don't know do i have to do this there we go sweet so now if you were like looking at this it'll open up so when you're browsing around in the help if you want help stack map dot push you'll go to here and if you were on like pop inside of here i press capital k it'll take me to here so like the help docs already do all that for you so as long as you just write them with a few little things like putting star around a tag like this um did i never um oh i think i just uh oh i accidentally deleted a line there there we go okay um as long as you put around that information then it'll just magically work like your plugin manager will build the tags for you and all that stuff so people will be able to just do help stack map.push and help stackmap.pop yeah so that's that's pretty much it now we just need to do oh i guess we need to edit the readme and say stacks of maps um documentation c help stack map dot nvim i think right did we say help stack map yeah yes nice not stat mac uh a plug-in for easily adding and then removing sets of mappings without losing what maps you had before something like that that's awesome and then there we go now we're done
Info
Channel: TJ DeVries
Views: 65,285
Rating: undefined out of 5
Keywords:
Id: n4Lp4cV8YR0
Channel Id: undefined
Length: 108min 23sec (6503 seconds)
Published: Fri Feb 18 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.