Afullyworking,mostfeature-richVue.jsterminalemulator.Seethedemoandcheckthedemosourcecode.
FeaturesParsesargumentswithgetoptsSupportsasynchronouscommandsBrowsehistory(with↑/↓)Autocompletionresolver(with↹)CustomizeterminalwithslotsSearchhistory(withCtrl+r)Installation$npminstallvue-command--saveUsageLet'sstartwithaverysimpleexample.Wewanttosend"Helloworld"toStdoutwhenenteringhello-world.
<template><vue-command:commands="commands"/></template><script>importVueCommand,{createStdout}from'vue-command'import'vue-command/dist/vue-command.css'exportdefault{components:{VueCommand},data:()=>({commands:{'hello-world':()=>createStdout('Helloworld')}})}</script>Nowamorecomplexone.Let'sassumewewanttobuildtheNanoeditoravailableinmanyshells.
WewillusetheprovidedenvironmentvariabletomakesuretheeditorisonlyvisiblewhenthiscommandisexecutingandinjectafunctioncalledterminatetotelltheterminalthatthecommandhasbeenfinishedwhentheuserentersCtrl+x.Furthermore,weinjectthesetIsFullscreenfunctiontoswitchtheterminalintofullscreenmode.
<template><divv-if="environment.isExecuting"><textarearef="nano"@keydown.ctrl.88="terminate">Thisisatexteditor!PressCtrl+xtoleave.</textarea></div></template><script>exportdefault{inject:['setIsFullscreen','terminate'],created(){this.setIsFullscreen(true)},mounted(){this.$refs.nano.focus()}}</script>Nowthecommandhastoreturnthecomponent.
<template><vue-command:commands="commands"/></template><script>importVueCommandfrom'vue-command'import'vue-command/dist/vue-command.css'importNanoEditorfrom'@/components/NanoEditor.vue'exportdefault{components:{VueCommand},data:()=>({commands:{nano:()=>NanoEditor}})}</script>PropertiesTherearetwotypesofcommands:Built-inandregularones.Inmostcasesregularcommandsareappropriate.Built-incommandsprovidehigherflexibility,seesectionBuilt-informoreinformation.
Somepropertiescanbechangedbytheterminal,therefore,thesyncmodifierhastobeadded.
PropertyTypeDefaultSyncDescriptionautocompletion-resolverFunctionnullNoSeeAutocompletionresolverbuilt-inObject{}NoSeeBuilt-insectioncommandsObject{}NoSeeCommandssectioncursorNumber0YesSetstheStdincursorpositionevent-listenersArray[EVENT_LISTENERS.autocomplete,EVENT_LISTENERS.history,EVENT_LISTENERS.search]NoSeeEventlistenerssectionexecutedSetnewSet()YesExecutedprograms,see"Overwritingexecutedfunctions"help-textStringTypehelpNoSetstheplaceholderhelp-timeoutNumber4000NoSetstheplaceholdertimeouthide-barBooleanfalseNoHidesthebarhide-promptBooleanfalseNoHidestheprompthide-titleBooleanfalseNoHidesthetitlehistoryArray[]YesExecutedcommandsintroStringFastenyourseatbelts!NoSetstheintrois-fullscreenBooleanfalseYesSetstheterminalfullscreenmodeis-in-progressBooleanfalseYesSetstheterminalprogressstatusnot-foundStringnotfoundNoSetsthecommandnotfoundtextparser-optionsObject{}NoSetstheparseroptionspointerNumber0YesSetsthecommandpointerpromptString~neil@moon:#NoSetsthepromptshow-helpBooleanfalseNoShowstheplaceholdershow-introBooleanfalseNoShowstheintrostdinString''YesSetsthecurrentStdintitleStringneil@moon:~NoSetsthetitleCommandscommandsmustbeanobjectcontainingkey-valuepairswherekeyisthecommandandthevalueisafunctionthatwillbecalledwiththegetopsarguments.ThefunctioncanreturnaPromiseandmustreturnorresolveaVue.jscomponent.Toreturnstringsornothinguseoneoftheconvenienthelpermethods:
FunctionDescriptioncreateStdout(content:String,isInnerText:Boolean,isEscapeHtml:Boolean,name:String,...mixins:Array):ObjectReturnsaStdoutcomponentcontainingaspanelementwithgiveninnercontentcreateStderr(content:String,isEscapeHtml:Boolean,name:String,...mixins:Array):ObjectReturnsaStderrcomponentcontainingaspanelementwithgiveninnercontentcreateDummyStdout(name:String,...mixins:Array):ObjectReturnsadummyStdouttoshowaStdinHelpermethodscanbeimportedbyname:
import{createStdout,createStderr,createDummyStdout}from'vue-command'Ifnoneofthehelpermethodsisused,thecommandhastobemanuallyterminatedinsidethecomponent.Nexttoterminationit'spossibletoinjectthefollowingfunctionstomanipulatetheterminalorsignalanevent:
FunctionDescriptionemitExecuteEmitcommandexecutioneventemitExecutedEmitcommandexecutedeventemitInput(input:String)EmitthecurrentinputsetCursor(cursor:Number)SetcursorpositionsetIsFullscreen(isFullscreen:Boolean)ChangeiftheterminalisinfullscreenmodesetIsInProgress(isInProgress:Boolean)ChangeiftheterminalisinprogresssetPointer(pointer:Number)SetcommandhistorypointersetStdin(stdin:String)SetthecurrentStdinterminateExecutescommonfinaltasksaftercommandhasbeenfinishedFunctionscanbeinjectedintoyourcomponentbyname:
inject:['setIsFullscreen','setIsInProgress','terminate']Inyourcomponentyouhaveaccesstoacontextandanenvironmentvariable.Theenvironmentvariablecontainsthefollowingproperties(notethatbuilt-incommandshavetotakecarebytheirselvesabouttheterminalsstate):
PropertyDescriptionisExecuting:BooleanIsthecurrentcomponentexecutingisFullscreen:BooleanIstheterminalinfullscreenmodeisInProgress:BooleanIsanycommandactiveThecontextvariablecontainsthefollowingproperties:
PropertyDescriptioncursor:NumberCopyofcursorpositionatStdinexecuted:SetCopyofexecutedprogramshistory:ArrayCopyofexecutedcommandsparsed:ObjectParsedgetopsargumentspointer:NumberCopyofhistorycommandpointerstdin:StringCopyofStdinBuilt-inBuilt-incommandsprovidemorecontrolovertheterminalsbehaviour.Ontheotherside,theyhavetotakecareabouteveryregularcommandstep.Asamatteroffact,regularcommandsarejustcallinghelpermethodsorchangepropertieswhichcouldbealsocalledorchangedbybuilt-incommands.Regularcommandscanbeseenasafacadetobuilt-incommands.
Sincebuilt-incommandscancaptureanycommand,it'snecessarytotakecareofautocompletionandthecommandnotfoundexperience.
Thefirstargumentthatiscalledwithinthebuilt-incommandistheunparsedStdin.It'spossibletouseacustomparseratthisplace.Thesecondargumentistheterminalinstance.YoucanusethecommandNotFoundmethodifnobuilt-inorregularcommandhasbeenfound.
Tofullysimulatearegularcommandcircleabuilt-incommandhastofollowthesesteps:
CallsetIsInProgresswithtruetotellthereisacommandinprogressAddtheprogrammtotheexecutedSetpropertyIncreasethehistorypointerwithsetPointerExecuteactualtaskPushtheStdoutcomponentintothehistorypropertyCallsetIsInProgresswithfalsetotellthereisnocommandinprogressanymoreAutocompletionresolverItispossibletoprovideafunctionthatiscalledwhentheuserhitsthe↹key.Thisfunctionneedstotakecareoftheautocompletionexperienceandshouldmakeusageofpropertieslikehistoryandstdin.Thefollowingshowsapossible,simpleautocompletionfunction:
this.autocompletionResolver=()=>{//Makesureonlyprogramsareautocompleted.Seebelowforversionwithoptionsconstcommand=this.stdin.split('')if(command.length>1){return}constautocompleteableProgram=command[0]//Collectallautocompletioncandidatesletcandidates=[]constprograms=[...Object.keys(this.commands)].sort()programs.forEach(program=>{if(program.startsWith(autocompleteableProgram)){candidates.push(program)}})//Autocompletionresolvedintomultipleresultsif(this.stdin!==''&&candidates.length>1){this.history.push({//Buildtableprogrammaticallyrender:createElement=>{constcolumns=candidates.length<5?candidates.length:4constrows=candidates.length<5?1:Math.ceil(candidates.length/columns)letindex=0lettable=[]for(leti=0;i<rows;i++){letrow=[]for(letj=0;j<columns;j++){row.push(createElement('td',candidates[index]))index++}table.push(createElement('tr',[row]))}returncreateElement('table',{style:{width:'100%'}},[table])}})}//Autocompletionresolvedintooneresultif(candidates.length===1){this.stdin=candidates[0]}}Advancedversionwithoptionautocompletionthis.autocompletionResolver=()=>{//Preservecursorpositionconstcursor=this.cursor//Reverseconcatenateautocompletableaccordingtocursorletpointer=this.cursorletautocompleteableStdin=''while(this.stdin[pointer-1]!==''&&pointer-1>0){pointer--autocompleteableStdin=`${this.stdin[pointer]}${autocompleteableStdin}`}//Dividebyargumentsconstcommand=this.stdin.split('')//Autocompleteableisprogramif(command.length===1){constautocompleteableProgram=command[0]//Collectallautocompletioncandidatesconstcandidates=[]constprograms=[...Object.keys(this.commands)].sort()programs.forEach(program=>{if(program.startsWith(autocompleteableProgram)){candidates.push(program)}})//Autocompletionresolvedintomultipleresultsif(this.stdin!==''&&candidates.length>1){this.history.push({//Buildtableprogrammaticallyrender:createElement=>{constcolumns=candidates.length<5?candidates.length:4constrows=candidates.length<5?1:Math.ceil(candidates.length/columns)letindex=0consttable=[]for(leti=0;i<rows;i++){constrow=[]for(letj=0;j<columns;j++){row.push(createElement('td',candidates[index]))index++}table.push(createElement('tr',[row]))}returncreateElement('table',{style:{width:'100%'}},[table])}})}//Autocompletionresolvedintooneresultif(candidates.length===1){//MutatingStdinmutatesthecursor,sowe'vetowaittopushittotheendconstunwatch=this.$watch(()=>this.cursor,()=>{this.cursor=cursor+(candidates[0].length-autocompleteableStdin.length+0)unwatch()})this.stdin=candidates[0]}return}//Checkifoptionmightbecompletedalreadyoroptionislasttokensif((this.stdin[cursor]!==''&&this.stdin[cursor]!=='')&&typeofthis.stdin[cursor]!=='undefined'){return}//Gettheexecutableconstprogram=command[0]//Checkifanyautocompleteableexistsif(typeofthis.options.long[program]==='undefined'&&typeofthis.options.short[program]==='undefined'){return}//Autocompleteableislongoptionif(autocompleteableStdin.substring(0,2)==='--'){constcandidates=[]this.options.long[program].forEach(option=>{//Ifonlydashesarepresent,userrequestsalloptionsif(`--${option}`.startsWith(autocompleteableStdin)||autocompleteableStdin==='--'){candidates.push(option)}})//Autocompletionresolvedintooneresultif(candidates.length===1){constautocompleted=`${this.stdin.substring(0,pointer-1)}--${candidates[0]}`constrest=`${this.stdin.substring(this.cursor)}`//MutatingStdinmutatesthecursor,sowe'vetowaittopushittotheendconstunwatch=this.$watch(()=>this.cursor,()=>{this.cursor=cursor+(candidates[0].length-autocompleteableStdin.length+2)unwatch()})this.stdin=`${autocompleted}${rest}`return}//Autocompletionresolvedintomultipleresultif(autocompleteableStdin==='--'||candidates.length>1){this.history.push({//Buildtableprogrammaticallyrender:createElement=>{constcolumns=candidates.length<5?candidates.length:4constrows=candidates.length<5?1:Math.ceil(candidates.length/columns)letindex=0consttable=[]for(leti=0;i<rows;i++){constrow=[]for(letj=0;j<columns;j++){row.push(createElement('td',`--${candidates[index]}`))index++}table.push(createElement('tr'
评论