Build a Terminal Wizard in Go (Bubble Tea Tutorial)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right I'm going to show you how we can create this wizard and all it's going to do is it's just going to Output the results of the form Fields so what I'm hoping that this tutorial is going to be able to do for you is show you how you can create these different form Fields add some custom styling to them and do something with the output so whatever you might want to do with that output is completely up to you in this case I just wanted to keep it as simple as possible so we are just having it print to our terminal I all right so let's start writing this let's create a new directory called wizard and in here let's go ahead and do a go mod init for uh wizard tutorial and then I'll open up a main.go and then we'll just do the basic implementation of the TDOT model interface so to do that we need our init update and view functions let's just create a new data type like a model and we'll just make that an empty struct let's go ahead and Implement init or model and here we're just gonna do a knit and this returns ATL commands and we can just return Yale and then we'll do one for update and then for this one it takes a message and it returns a TDOT model and a TDOT command and here I'm just going to turn M coming ill let's do the view so we'll do it model view and then this one returns a string but for this one let's return uh just hello world is fine let's do our main method and this is where we're going to start up our program for the sake of debugging let's use the log to file in case we need to do any prints or anything like that and I don't think I've shown you how to do that already in any existing tutorials so we'll do that so T DOT log to file and we'll get a debug.log is the file that it's going to Output to and then just a prefix which I'm just going to leave that as debug and then I'll do an if error does not equal nail check and then because we've got that open let's go ahead and defer the file closing and let's start up our bubble tea program so our program we're going to create a new T program new program and then we're going to put our model so we need to have basically just a model in here and then let's say that we want to use the alt screen so we'll do with alt screen let's do our let's run it let's start it up so then we'll do is p dot run since the last time that we created a tutorial it's actually been updated to run instead of start and then here I'll just output the error if there is an error all right so that's the basics let me actually do a quick change to our updates that we can actually quit the application otherwise it's just gonna like exist forever and hang there so let's do a little switch statement so we'll do it based on the type of the message so we'll do message equal to message I can't type message.type and then we'll do a case for T dot key message and then we'll do a switch on message.string which is just the exact string representation of what we see I think this is pretty much the easiest to work with honestly you can also have keybinds that you check for but I'll probably just show that I'll show that maybe in the next tutorial because both are both work fine and we don't we don't recommend one over the other so we're going to switch over the value of the message.strings that we can just do a direct uh direct comparison here so I'm going to make it so that control C is how we quit and when that gets hit we're going to return M comma T dot quit suspend that do a little go run because we've now added some dependencies there with bubble tea and then let's try running it perfect so that's what we want and if I hit Ctrl C it'll bring us back so that is the first part it's just outlining the TDOT model interface and implementing that with one of our models all right let's make this model look a little bit more like a little questionnaire make a questions array it's a mirror of strings a width and a height and let's create a instructor for it so we'll say new and then we'll get in a string of questions and this is going to return a pointer 2 model and then we'll just return new model with uh questions there we go perfect and then what we'll do here in our update we will get the switch here for when it is a TDOT Windows size message and so basically that window size message gets called or like gets sent to our application on Startup and on window resize so you're only going to get this message once which is why we're storing the width and the height that we get given in the model so that basically every single time that we're creating a new or like displaying a new question we can we can set the width and the height more accurately so in this one we'll do m.width is equal to message.width and then do the same thing for height and then what we should do is we'll just do a little check here so we'll say like if the width is equal to zero then we'll say that it's loading because that means that it hasn't received that yet so we don't want it to render anything and then once it has received the Windows size message you can say loaded let's change this log out fatal to just return the error instead of trying to format it that and then let's create our model here now so we'll do we'll just do m is equal to new and that's going to take a list of questions so let's create that questions array we're going to do this and we'll do uh what is your what is your name and then let's do another one for what is your favorite editor and maybe a third that's going to be probably a long form question which is what is your favorite quote perfect and then here we can call we'll put the questions in there and then we'll move this to be m H perfect so now we've actually made it look a little bit more like a questionnaire all right let's check out what that looks like right now okay so it just loaded it didn't take very long to get that message back so let's make more changes to it let's add an answer field because obviously right now we're just doing random outputs so let's go back to our neovim and let's add a text input field to our model so with that because it's a text input field we're only going to be able to do short answer questions but we'll fix that later so for now let's add answer field and then we have to initialize that so we'll say uh answer field is equal to a text input dot new here let's say answer field is equal to answer field and then let's also add it to our view so we'll actually use lip gloss.join vertical which is a great way to manage your layouts in the terminal is you can just do either join it vertical or join horizontal and it will like join your different components or okay strings uh together so it's a great way to manage how you want to lay things out and go ahead and do that except I meant to do vertical not horizontal and then we'll do lip gloss Dot Center perfect and then we gotta do MDOT questions and I also just realized we don't have a current index to keep track of what question we're on so I'll have to add that to our model as well and then m.ansorfield Dot View so we'll call The View on the text input for our current uh for the text input model but then I also said I need an index that is going to be an INT perfect actually I'll just put that up here there you go and then uh what is my lintersing I could not import okay we're good we're good so maybe for now I think it's okay we'll just leave the favorite quote in there and then we'll fix that later so let's do a go run oh right we got to do go montidy first and just clean it up grab the text input and all the other dependencies and then do go run okay what is your name and as you can see we can't type anything and the layout kind of looks bad it's working but at what cost you know what I mean oh because this looks terrible let's add some styling so that it looks nice and we feel a little bit more motivated and like we're getting somewhere so we'll add The Styling and then we'll add the interactivity so first part styling let's go to the top here oh I think I need to let me just reopen that file so it can figure out that I actually did clean up the Imports but let's create a struct that has all of our Styles in it and here we'll just have let's have a border color which is going to be a type lip gloss.color and then we'll have an input fields which is going to have its own styling so I'll have a lip gloss dot style as the type for that and then let's have a function that Returns what our default styles are let's do default Styles and this returns a style struct and then what we're going to do here is let's just create easy enough a new new Styles and then what we're going to do is let's set s dot border color to be lip gloss.color and we'll just give it a random value honestly uh 36 I guess and then s.input field uh let's make this a new lip gloss style and let's give it a border so we'll say border foreground and we're going to make this the Border color that we've just declared and then let's say that the Border Style border style let's make it just a normal border the normal border my head's in the way here hold on there we go I'll make that a normal border and then let's add some padding to it just to see what that looks like and let's set the width to like 80 I guess and then let's also add some placeholder text to our answer field so we'll do answer Fields dot placeholder and we're going to make that your answer here perfect and let's also create oh I didn't return anything so here return s which is our Styles let's create new Styles so we'll do Styles equal to default Styles so it's just going to initialize it with all our defaults and then I'll say styles is equal to Styles and I will add that here there we go all right I think we don't have any issues there I am just going to clean this up real quick I don't think we actually applied the Styles so let's fix that all right uh we did not render anything with the Styles so here let's do m.styles dot input field.renderperfect and then we're going to make this m dot answer Fields Dot View this string is going to get rendered with the styling that we had just declared in theory all right perfect so that looks a lot better I don't like the positioning that much let's fix that so we've got the join vertical in there but what we want to do is we want to place it right in the middle of our terminal so let's use lip gloss dot place for that crazy stuff I know I know all right lip gloss.place which is going to allow us to place it wherever we want so what we'll do is we'll have our m.width and our m.height and that's where we're going to place it and then we're going to set it to lip gloss.center and lip gloss.center so it's centered on on both sides and then move this like that should be um yes and then I need a comma here as that is one of the values there we go okay let's add some interactivities that I can actually do something with this question because we're adding interactivity all of the work that we're going to be doing is in the update function so what we're going to do here is let's make it so that when we hit enter it's going to bring us to the next question so when we hit enter as a key message then let's increment our index increment the index and then set m dot answer field to done all right and then we have to return the updated model otherwise it won't do anything so let's do that and then another thing that we want to do is let's make it so that we're actually sending the message to our answer field AKA our text input so we need to pass that message along in order for any interactivity to happen with that bubbles component so let's do that so we basically just need to do that set a TDOT command variable and have m.answer field dot update set that so whatever that returns and we'll have it run that command and then we also just need to declare that variable it's a TDOT command nice that should be all the changes that we need to make but let's double check no so that's not working so there's a couple things that also need to happen which is that we need to focus the input field so we've already set the placeholder up here when we initialize it but let's say hey we want to focus it right away it's important it's important us so so now I can actually type in here and when I hit enter I mean that's not really what we want we'd actually probably rather clear that value but we'll we'll fix that don't worry but as you can see it's actually looking pretty functional and we can flip through all the different ones and also as you can see we've now introduced a new bug that we're also going to have to fix let's fix that bug that we just introduced so I'm going to add a little helper function down here that's going to be for bringing us to the next value so we'll have a model and we'll make it uh next is just what the function is going to be called so here if we do m dot we'll do m dot index so if it's less than the length of m dot questions minus one okay then we'll increment it so we'll do m dot index plus plus and then otherwise we'll just set my spacebar's broken okay we'll set m dot index to zero so it'll just set it to the beginning we also need to create a question struct which I've just created here so we've got the question and the associated answer for that question and then what we're going to do is in our update we're going to set our current let's go ahead and do that in in here let's make a variable for the current so current is equal to we'll make it a pointer so that we can manipulate it if we need to and we'll make it for the current question which is going to be m.question at the current index one problem is that we actually still have our questions defined as an array of strings but it should be an array of questions now let's change this to be a question an array of questions yes let's do a function for new question and this is going to take in a question string and it's going to return [Music] a question I think we can actually just get it to return normal one and we'll just return a question with that there we go and then of course the default value for this for first string is empty string so that will work and yes and then here we need to do something about this so this is going to be an array of questions now so I'll do a little a little bit of refactoring here aren't declared but not used that's okay so what we're going to do is we're actually going to do a current dot answer we'll set that to the current value and that has to happen before we switch the current index there we go and we should clear it so before we clear it let's do m.ansorfields.set value and we'll decide it to empty so we'll clear it after each one let's see what that looks like um oh right I haven't fixed that yet so we gotta do a new question for each of these foreign so I just used a little macro action to quickly replace all of that and I do believe only got a tiny bit of problems here cannot use him no questions a string oh so here we got to change this I'm not question saw index that would be thought question there we go because now it's it's no longer a string it's now an instruct with a question field so let's run that and see what that looks like right so I can still interact with it editor neovim favorite quote there and now it's just kind of looping it's not really doing anything with the answers that we're giving it and if we want actually as a good practice for just getting familiar with bubble tea and how you could check these things what we could do is log the question let's do a log dot print we'll do a little print line sure sure and actually let's do printf and then we can do question and then have that be the question and then the new answer and that will be current dot question and current dot answer and we'll just we'll just see because then that way we can check the debug.log file after so go ahead and go run that and now I'll say bash money is my name favorite editor neovim favorite quote yes and now because we switch back I gotta double check it might have cleared the answer but let's just take a look all right so it looks like it is registering each of our questions and answers which is nice that is what we want let's fix it a little bit though because we do want to be able to have a long form and short form questions and that adds a little bit of complexity because the text input and text area bubbles so a lot of our bubbles don't actually implement the TDOT model interface which is something we are thinking about in the back end on whether we want to change that but because they don't implement the TDOT model interface we have to do a little bit of a work around to get the functionality that we want to be able to swap between long and short questions while having basically all of them be considered a question we're gonna have a type input which is going to be an interface and this is pretty much going to be a wrapper for both text area and text input so that we can swap them in and out so that each question will have either a long form or short form answer so I'm actually going to leave it as a blank interface for a second here because I want to show you how we know what to implement for the interface we've got our question here we've got all the other functionality for our model our main model what I'll do here is I'll create a new function that will return a question but the input type is going to vary because it's going to be a short question so we're going to do new short question and you can have a question cue string this I can also just change the queue but we'll just leave it we'll make it on all questions that's what all that's what tab completes for right right cricket noises okay and then here we'll do a new question yes thank you and then we will pass in the question and what we're going to modify here is we want to modify the input type which we don't have a field for yet so this is going to be the input type and it's going to be the interface crazy stuff I know I know bear with me what we're doing here is we're going to assign this to our field and we got to create that field still so this would be for a new short answer field which doesn't exist yet okay but it will okay just hold on and then we'll return our new question so we're gonna have that for the short question and then we're gonna have something similar for a long question so a long question and then here we'll just change the short answer field to a long answer field oh it's confused hold on I have too many repeating things uh let's make this one new okay that should have fixed most of this okay there we go so now those are not declared what we're going to do is we're going to implement these in the input.go new short answer field I'm just gonna like yoink these just join them you want to put them in here okay just that we remember what we have to create all right so these are new functions that we got to write and what's the main difference between these two things is that one is going to be using the text input bubble and the other one is going to be using the text area which is a multi-line input so what we'll do here is we got to create let's create a struct oops a short answer field and that's going to be a struct and it's going to have a text input and it's going to obviously be a type a text input.model text input.model perfect and then we'll do something similar for the long answer field which we'll put right here and we'll just change this to Long and change this to text area okay all right so those are the differences between those two things and all we're going to do here is we're going to return a new long answer field and that is pretty straightforward so we'll just do Fields equal to uh it will say text area text area dot new ta we'll change it ta tax area.new and then what we'll do is we'll just return a long answer fields I'm really struggling to type today okay don't don't judge me okay I haven't fixed my LSP just ignore me okay actually don't ignore me this is a tutorial okay and then we'll put TA in there so that's our text area and then we'll pretty much do the same thing for the short answer fields and we'll just change any long to Short okay there we go and this is going to be a text input text input dot new and I'm just going to change it to TI makes a little bit more sense all right so now we've got these new structs that are wrappers for our different input types so we've got our short answer field or our long answer field and we're going to need to call different things from both text area and text input which thankfully have pretty much all of the same methods but we're going to have to use input as the wrapper let's go to our bottom here and here we're actually going to change this new question so this one's going to be a new short question and this one's going to be a new short question as well and this one's going to be a long question so we'll do no long question oops did not finish typing there there we so now we're actually using those that function that we just came up with and we're gonna have to change some things around here as well so the answer field is irrelevant now because we're actually we're not storing that in our main model anymore because it'll vary so let's delete that field from our main model and see what errors it gives us so now this is where we're going to have to use the it would basically be the question dot input field uh this one yeah so that that doesn't need to be initialized anymore where else do we have errors okay so then this one is going to be current so current dot input so the value is undefined because we this is a method that I know exists for both text area and text input but we haven't implemented it in the wrapper so that's the first thing that we're going to want to put in here so in this interface we say okay we need the value need a value function and that returns a string so now that should get rid of this problem and then here we need to set the current this would be current input dot value we can just do current dot actually we don't even need to set we don't need to reset this value but what we do need to do is have it blur the current input so that you basically it's like locked so it's no longer focused and it'll switch its focus to the next input so we'll do current dot input dot blur which is a command okay so it removes the focus data on the model when the model is blurred it cannot receive keyboard input and the cursor will be hidden okay perfect and that what is the sorry what's the signature on that it doesn't return anything so perfect we can just have the blur in there and it'll uh it'll do that and then don't forget that because this is an interface and we want each of these structs to implement the interface we're going to have to have like wrapper functions on them so let's let's do that because we've already got a couple of functions in there and I don't want you to get confused so let's create a phone call Value and this is going to be super easy to do because all we're doing short answer field and all we're doing is actually returning the value that it would be for the text input or text area so just be dot value that's all we're doing is just a wrapper and we'll do the same thing here and we'll make it and this is going to be a long answer field and this will be La dot text area there we go all right so you get the gist of how we're going to do that and then same with blur so I can actually copy this and we'll just make it blur instead of value color and then blur perfect oh we don't need to return anything so we can just call that I just hit that by accident uh value type oh that's my bed there we go okay and then we'll do the same thing for the long answer so this is again just to remove the focus from the current field that we've been writing in all right great so now we've got a couple of those implemented and answer field is undefined yes that's okay so what we're going to do here instead is again just replace that directly with current dot input and which means here let's print dot input is now replacing everywhere that we had our answer field before and this as you can see it says okay current dot input.update is undefined it type input does not have a function called update which is totally fine let's add it to the interface so here we'll have oops I stole the spacing there and then here we'll have a TDOT message as the response type and then it will return a t dot model and a t dot command all right perfect actually because we're not implementing the TDOT model interface here we're actually just going to make this return an input which is anything that implements this interface and at Doc command so if you'd like we can Implement that as well so here is the function signature and let's do Funk and essays short answer Fields update and that can be a pointer that's fine and all we're going to do here same thing as before return a essay dot text input dot update with the message in there and this is going to be message sorry we're not quite going to return that we're going to do uh similar to what we would normally do with a wrapper with a wrapped update when we're using different models which is we're going to have this command variable and then we're going to do a DOT text input dot text input comma command is equal to sorry essay I meant sa dot text input is equal to that and then we will return our current model comma the command which is normally how you would like this is always how you would kind of pass messages along to the sub components and stuff anyway so hopefully that's a nice little refresher or insight for you like the simplest possible update and then this one we will change to Long answer field and LA and then this one la I wasn't being so lazy I would do that smarter but it's fine actually let's do it for this one so in here we'll go ahead and replace a text let's replace input with area perfect there we go all right and now back here we won't get that anymore uh cannot use value of type what have I done value of type function as T dot command my bad so we actually do want to return a TDOT message here because it is a command it's a TDOT command so we got to change the signature a little bit in our interface and we also need to update both of these blurs otherwise those will not work so TDOT message and then we'll just return this there we go and B Dot message perfect that should be it all right and then answer field again we're just going to return that with a current which we don't have defined here and it will be current dot input is what we're going to switch it out for and here let's add current is equal to M naught questions at the current index .index perfect and again it's telling us hey by the way you have an implanted view yet which is cool we can do that let me let me just do a quick little separation here so it's a little bit easier to a little bit more organized and I left my caps key on there we go all right so let's do a function for up for View and these because we're not implementing the TDOT model interface you can make them pointers if you want so here we'll just return a string and it's going to be sa.m dot text input Dot View is what we are returning all right Claire's mud good good I'm glad okay and here let's change let's change essay to LA and let's change short answer oops change short to Long perfect and text area or text input to text area all right perfect so I don't think we added it yeah we haven't added it to our interface which is why that error still exists so there's View and that returns a string so now when I save that should be fixed okay we got no more Diagnostics so it's not yelling at us for anything else which means it should in theory have everything implemented but first let's double check that this is all working the way that we want it to so instead of having this log to the file I mean the goal of the application anyway is to have it basically be printing your question and answer at the end so maybe let's just Implement that right now let's go to our view and what we'll do here is so once we've answered all the questions we wanted to Output that so I think we're going to need a new field for that in our main model to let us know that hey by the way we're done answering all the questions so let's add we're going to add a field called done which is going to be Boolean but to do that let's have our output string and just go over all of the values that are currently in the range of MDOT questions and let's do a little bit of a structured output so we'll just do that and we'll Sprint F nothing crazy and question answer and then we'll get that from qdoc question and Q dot answer perfect I did not spell this word right nice nice okay and then m dot done is undefined which is fine let's go to our model construct and let's add done which is a bull so we'll say that when we are at the last index then we'll set m is done so if m.index is equal to the length uh I'm not questions minus one then m dot done is true all right and then we don't need that log printing anymore all right let's see what this looks like I can't type anything so that's not a good sign let's fix that honestly I'm surprised this even runs because I still have this answer field happening up in the new this one is where I forgot to put in the placeholder and to focus it so for both of these I'll want to do a ti.focus all right and we also want to probably add a placeholder so let's make that uh to adult placeholder is equal to your answer here all right and let's also put that in the long answer and then should work TM uh oh now we can type in it yay good stuff good stuff all right let's try it so bash bunny neovim yes okay and now it's just kind of looping and we can change it and all that but we want it to end once we've accomplished what we wanted to accomplish which we're currently not doing so what we actually want to do here which we forgot to do aka me uh we got to return the output so that the function actually returns isn't that crazy I know I know you're welcome keeping those plot twists in there all right and let's go run and now if I do it it might work yes okay very ugly output maybe let's actually at least add a new line or something like that so lip gloss.place uh what we want here let's let's let's add a new line here and then that's pretty much it all right go run let's add bash bunny let's add neovim and yes as the quote yes it's always a good word good quote sure that is the basics of the wizard if you'd like you can check out the source code Linked In the description to check it out for yourself feel free to ask any questions join the Discord ideally and ask questions there or ask questions where the code lives uh either one thank you so much for watching give us a like if you like this format you love seeing the tutorials let us know with some likes and some comments alright see you next one bye
Info
Channel: Charm CLI
Views: 29,666
Rating: undefined out of 5
Keywords:
Id: Gl31diSVP8M
Channel Id: undefined
Length: 37min 15sec (2235 seconds)
Published: Tue Mar 07 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.