vi.c 137 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * tiny vi.c: A small 'vi' clone
  4. * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
  5. *
  6. * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  7. */
  8. //'vi' is a text editor. More specifically, it is the One True
  9. //text editor <grin>. It does, however, have a rather steep
  10. //learning curve. If you are not already comfortable with 'vi'
  11. //you may wish to use something else.
  12. #include <rtthread.h>
  13. #include <optparse.h>
  14. #include "vi_utils.h"
  15. /* This struct is deliberately not defined. */
  16. /* See docs/keep_data_small.txt */
  17. struct globals;
  18. /* '*const' ptr makes gcc optimize code much better.
  19. * Magic prevents ptr_to_globals from going into rodata.
  20. * If you want to assign a value, use SET_PTR_TO_GLOBALS(x) */
  21. struct globals *ptr_to_globals;
  22. #if ENABLE_LOCALE_SUPPORT
  23. #if ENABLE_FEATURE_VI_8BIT
  24. //FIXME: this does not work properly for Unicode anyway
  25. # define Isprint(c) (isprint)(c)
  26. #else
  27. # define Isprint(c) isprint_asciionly(c)
  28. #endif
  29. #else
  30. /* 0x9b is Meta-ESC */
  31. #if ENABLE_FEATURE_VI_8BIT
  32. # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
  33. #else
  34. # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
  35. #endif
  36. #endif
  37. #ifdef RT_USING_POSIX_TERMIOS
  38. #define isbackspace(c) ((c) == term_orig.c_cc[VERASE] || (c) == 8 || (c) == 127)
  39. #else
  40. #define isbackspace(c) ((c) == 8 || (c) == 127)
  41. #endif /* RT_USING_POSIX_TERMIOS */
  42. enum {
  43. MAX_TABSTOP = 32, // sanity limit
  44. // User input len. Need not be extra big.
  45. // Lines in file being edited *can* be bigger than this.
  46. MAX_INPUT_LEN = 128,
  47. // Sanity limits. We have only one buffer of this size.
  48. MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
  49. MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
  50. };
  51. // VT102 ESC sequences.
  52. // See "Xterm Control Sequences"
  53. // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
  54. #define ESC "\033"
  55. // Inverse/Normal text
  56. #define ESC_BOLD_TEXT ESC"[7m"
  57. #define ESC_NORM_TEXT ESC"[m"
  58. // Bell
  59. #define ESC_BELL "\007"
  60. // Clear-to-end-of-line
  61. #define ESC_CLEAR2EOL ESC"[K"
  62. // Clear-to-end-of-screen.
  63. // (We use default param here.
  64. // Full sequence is "ESC [ <num> J",
  65. // <num> is 0/1/2 = "erase below/above/all".)
  66. #define ESC_CLEAR2EOS ESC"[J"
  67. // Cursor to given coordinate (1,1: top left)
  68. #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
  69. #define ESC_SET_CURSOR_TOPLEFT ESC"[H"
  70. //UNUSED
  71. //// Cursor up and down
  72. //#define ESC_CURSOR_UP ESC"[A"
  73. //#define ESC_CURSOR_DOWN "\n"
  74. #if ENABLE_FEATURE_VI_DOT_CMD
  75. static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~";
  76. #endif
  77. static const char *_show_usage = "Usage: vi [FILE]\n";
  78. enum {
  79. YANKONLY = FALSE,
  80. YANKDEL = TRUE,
  81. FORWARD = 1, // code depends on "1" for array index
  82. BACK = -1, // code depends on "-1" for array index
  83. LIMITED = 0, // char_search() only current line
  84. FULL = 1, // char_search() to the end/beginning of entire text
  85. PARTIAL = 0, // buffer contains partial line
  86. WHOLE = 1, // buffer contains whole lines
  87. MULTI = 2, // buffer may include newlines
  88. S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
  89. S_TO_WS = 2, // used in skip_thing() for moving "dot"
  90. S_OVER_WS = 3, // used in skip_thing() for moving "dot"
  91. S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
  92. S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
  93. C_END = -1, // cursor is at end of line due to '$' command
  94. };
  95. /* vi.c expects chars to be unsigned. */
  96. /* busybox build system provides that, but it's better */
  97. /* to audit and fix the source */
  98. struct globals {
  99. /* many references - keep near the top of globals */
  100. char *text, *end; // pointers to the user data in memory
  101. char *dot; // where all the action takes place
  102. int text_size; // size of the allocated buffer
  103. // the rest
  104. #if ENABLE_FEATURE_VI_SETOPTS
  105. smallint vi_setops; // set by setops()
  106. #define VI_AUTOINDENT (1 << 0)
  107. #define VI_EXPANDTAB (1 << 1)
  108. #define VI_ERR_METHOD (1 << 2)
  109. #define VI_IGNORECASE (1 << 3)
  110. #define VI_SHOWMATCH (1 << 4)
  111. #define VI_TABSTOP (1 << 5)
  112. #define autoindent (vi_setops & VI_AUTOINDENT)
  113. #define expandtab (vi_setops & VI_EXPANDTAB )
  114. #define err_method (vi_setops & VI_ERR_METHOD) // indicate error with beep or flash
  115. #define ignorecase (vi_setops & VI_IGNORECASE)
  116. #define showmatch (vi_setops & VI_SHOWMATCH )
  117. // order of constants and strings must match
  118. #define OPTS_STR \
  119. "ai\0""autoindent\0" \
  120. "et\0""expandtab\0" \
  121. "fl\0""flash\0" \
  122. "ic\0""ignorecase\0" \
  123. "sm\0""showmatch\0" \
  124. "ts\0""tabstop\0"
  125. #else
  126. #define autoindent (0)
  127. #define expandtab (0)
  128. #define err_method (0)
  129. #define ignorecase (0)
  130. #endif
  131. #if ENABLE_FEATURE_VI_READONLY
  132. smallint readonly_mode;
  133. #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
  134. #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
  135. #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
  136. #else
  137. #define SET_READONLY_FILE(flags) ((void)0)
  138. #define SET_READONLY_MODE(flags) ((void)0)
  139. #define UNSET_READONLY_FILE(flags) ((void)0)
  140. #endif
  141. smallint editing; // >0 while we are editing a file
  142. // [code audit says "can be 0, 1 or 2 only"]
  143. smallint cmd_mode; // 0=command 1=insert 2=replace
  144. int modified_count; // buffer contents changed if !0
  145. int last_modified_count; // = -1;
  146. int cmdline_filecnt; // how many file names on cmd line
  147. int cmdcnt; // repetition count
  148. char *rstart; // start of text in Replace mode
  149. unsigned rows, columns; // the terminal screen is this size
  150. #if ENABLE_FEATURE_VI_ASK_TERMINAL
  151. int get_rowcol_error;
  152. #endif
  153. int crow, ccol; // cursor is on Crow x Ccol
  154. int offset; // chars scrolled off the screen to the left
  155. int have_status_msg; // is default edit status needed?
  156. // [don't make smallint!]
  157. int last_status_cksum; // hash of current status line
  158. char *current_filename;
  159. #if ENABLE_FEATURE_VI_COLON_EXPAND
  160. char *alt_filename;
  161. #endif
  162. char *screenbegin; // index into text[], of top line on the screen
  163. char *screen; // pointer to the virtual screen buffer
  164. int screensize; // and its size
  165. int tabstop;
  166. int last_search_char; // last char searched for (int because of Unicode)
  167. smallint last_search_cmd; // command used to invoke last char search
  168. #if ENABLE_FEATURE_VI_UNDO_QUEUE
  169. char undo_queue_state; // One of UNDO_INS, UNDO_DEL, UNDO_EMPTY
  170. #endif
  171. #if ENABLE_FEATURE_VI_DOT_CMD
  172. smallint adding2q; // are we currently adding user input to q
  173. int lmc_len; // length of last_modifying_cmd
  174. char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
  175. int dotcnt; // number of times to repeat '.' command
  176. #endif
  177. #if ENABLE_FEATURE_VI_SEARCH
  178. char *last_search_pattern; // last pattern from a '/' or '?' search
  179. #endif
  180. #if ENABLE_FEATURE_VI_SETOPTS
  181. int char_insert__indentcol; // column of recent autoindent or 0
  182. int newindent; // autoindent value for 'O'/'cc' commands
  183. // or -1 to use indent from previous line
  184. #endif
  185. smallint cmd_error;
  186. /* former statics */
  187. #if ENABLE_FEATURE_VI_YANKMARK
  188. char *edit_file__cur_line;
  189. #endif
  190. int refresh__old_offset;
  191. int format_edit_status__tot;
  192. /* a few references only */
  193. #if ENABLE_FEATURE_VI_YANKMARK
  194. smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
  195. #define Ureg 27
  196. char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
  197. char regtype[28]; // buffer type: WHOLE, MULTI or PARTIAL
  198. char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
  199. #endif
  200. #ifdef RT_USING_POSIX_TERMIOS
  201. struct termios term_orig; // remember what the cooked mode was
  202. #endif
  203. int cindex; // saved character index for up/down motion
  204. smallint keep_index; // retain saved character index
  205. #if ENABLE_FEATURE_VI_COLON
  206. char *initial_cmds[3]; // currently 2 entries, NULL terminated
  207. #endif
  208. char readbuffer[KEYCODE_BUFFER_SIZE];
  209. #define STATUS_BUFFER_LEN 200
  210. char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
  211. #if ENABLE_FEATURE_VI_DOT_CMD
  212. char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
  213. #endif
  214. char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
  215. char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
  216. #if ENABLE_FEATURE_VI_UNDO
  217. // undo_push() operations
  218. #define UNDO_INS 0
  219. #define UNDO_DEL 1
  220. #define UNDO_INS_CHAIN 2
  221. #define UNDO_DEL_CHAIN 3
  222. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  223. #define UNDO_INS_QUEUED 4
  224. #define UNDO_DEL_QUEUED 5
  225. # endif
  226. // Pass-through flags for functions that can be undone
  227. #define NO_UNDO 0
  228. #define ALLOW_UNDO 1
  229. #define ALLOW_UNDO_CHAIN 2
  230. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  231. #define ALLOW_UNDO_QUEUED 3
  232. # else
  233. // If undo queuing disabled, don't invoke the missing queue logic
  234. #define ALLOW_UNDO_QUEUED ALLOW_UNDO
  235. # endif
  236. struct undo_object {
  237. struct undo_object *prev; // Linking back avoids list traversal (LIFO)
  238. int start; // Offset where the data should be restored/deleted
  239. int length; // total data size
  240. uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
  241. char undo_text[1]; // text that was deleted (if deletion)
  242. } *undo_stack_tail;
  243. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  244. #define UNDO_USE_SPOS 32
  245. #define UNDO_EMPTY 64
  246. char *undo_queue_spos; // Start position of queued operation
  247. int undo_q;
  248. char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
  249. # endif
  250. #endif /* ENABLE_FEATURE_VI_UNDO */
  251. };
  252. #define G (*ptr_to_globals )
  253. #define text (G.text )
  254. #define text_size (G.text_size )
  255. #define end (G.end )
  256. #define dot (G.dot )
  257. #define reg (G.reg )
  258. #define vi_setops (G.vi_setops )
  259. #define editing (G.editing )
  260. #define cmd_mode (G.cmd_mode )
  261. #define modified_count (G.modified_count )
  262. #define last_modified_count (G.last_modified_count)
  263. #define cmdline_filecnt (G.cmdline_filecnt )
  264. #define cmdcnt (G.cmdcnt )
  265. #define rstart (G.rstart )
  266. #define rows (G.rows )
  267. #define columns (G.columns )
  268. #define crow (G.crow )
  269. #define ccol (G.ccol )
  270. #define offset (G.offset )
  271. #define status_buffer (G.status_buffer )
  272. #define have_status_msg (G.have_status_msg )
  273. #define last_status_cksum (G.last_status_cksum )
  274. #define current_filename (G.current_filename )
  275. #define alt_filename (G.alt_filename )
  276. #define screen (G.screen )
  277. #define screensize (G.screensize )
  278. #define screenbegin (G.screenbegin )
  279. #define tabstop (G.tabstop )
  280. #define last_search_char (G.last_search_char )
  281. #define last_search_cmd (G.last_search_cmd )
  282. #if ENABLE_FEATURE_VI_READONLY
  283. #define readonly_mode (G.readonly_mode )
  284. #else
  285. #define readonly_mode 0
  286. #endif
  287. #define adding2q (G.adding2q )
  288. #define lmc_len (G.lmc_len )
  289. #define ioq (G.ioq )
  290. #define ioq_start (G.ioq_start )
  291. #define dotcnt (G.dotcnt )
  292. #define last_search_pattern (G.last_search_pattern)
  293. #define char_insert__indentcol (G.char_insert__indentcol)
  294. #define newindent (G.newindent )
  295. #define cmd_error (G.cmd_error )
  296. #define edit_file__cur_line (G.edit_file__cur_line)
  297. #define refresh__old_offset (G.refresh__old_offset)
  298. #define format_edit_status__tot (G.format_edit_status__tot)
  299. #define YDreg (G.YDreg )
  300. //#define Ureg (G.Ureg )
  301. #define regtype (G.regtype )
  302. #define mark (G.mark )
  303. #define restart (G.restart )
  304. #ifdef RT_USING_POSIX_TERMIOS
  305. #define term_orig (G.term_orig )
  306. #endif
  307. #define cindex (G.cindex )
  308. #define keep_index (G.keep_index )
  309. #define initial_cmds (G.initial_cmds )
  310. #define readbuffer (G.readbuffer )
  311. #define scr_out_buf (G.scr_out_buf )
  312. #define last_modifying_cmd (G.last_modifying_cmd )
  313. #define get_input_line__buf (G.get_input_line__buf)
  314. #if ENABLE_FEATURE_VI_UNDO
  315. #define undo_stack_tail (G.undo_stack_tail )
  316. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  317. #define undo_queue_state (G.undo_queue_state )
  318. #define undo_q (G.undo_q )
  319. #define undo_queue (G.undo_queue )
  320. #define undo_queue_spos (G.undo_queue_spos )
  321. # endif
  322. #endif
  323. #define INIT_G() do { \
  324. SET_PTR_TO_GLOBALS(vi_zalloc(sizeof(G))); \
  325. last_modified_count --; \
  326. /* "" but has space for 2 chars: */ \
  327. IF_FEATURE_VI_SEARCH(last_search_pattern = vi_zalloc(2);) \
  328. tabstop = 8; \
  329. IF_FEATURE_VI_SETOPTS(newindent--;) \
  330. } while (0)
  331. static void edit_file(char *); // edit one file
  332. static void do_cmd(int); // execute a command
  333. static int next_tabstop(int);
  334. static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
  335. static char *begin_line(char *); // return pointer to cur line B-o-l
  336. static char *end_line(char *); // return pointer to cur line E-o-l
  337. static char *prev_line(char *); // return pointer to prev line B-o-l
  338. static char *next_line(char *); // return pointer to next line B-o-l
  339. static char *end_screen(void); // get pointer to last char on screen
  340. static int count_lines(char *, char *); // count line from start to stop
  341. static char *find_line(int); // find begining of line #li
  342. static char *move_to_col(char *, int); // move "p" to column l
  343. static void dot_left(void); // move dot left- dont leave line
  344. static void dot_right(void); // move dot right- dont leave line
  345. static void dot_begin(void); // move dot to B-o-l
  346. static void dot_end(void); // move dot to E-o-l
  347. static void dot_next(void); // move dot to next line B-o-l
  348. static void dot_prev(void); // move dot to prev line B-o-l
  349. static void dot_scroll(int, int); // move the screen up or down
  350. static void dot_skip_over_ws(void); // move dot pat WS
  351. static char *bound_dot(char *); // make sure text[0] <= P < "end"
  352. static void new_screen(int, int); // malloc virtual screen memory
  353. #if !ENABLE_FEATURE_VI_UNDO
  354. #define char_insert(a,b,c) char_insert(a,b)
  355. #endif
  356. static char *char_insert(char *, char, int); // insert the char c at 'p'
  357. // might reallocate text[]! use p += stupid_insert(p, ...),
  358. // and be careful to not use pointers into potentially freed text[]!
  359. static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
  360. static int find_range(char **start, char **stop, int cmd); // return pointers for an object
  361. static int st_test(char *, int, int, char *); // helper for skip_thing()
  362. static char *skip_thing(char *, int, int, int); // skip some object
  363. static char *find_pair(char *, const char); // find matching pair () [] {}
  364. #if !ENABLE_FEATURE_VI_UNDO
  365. #define text_hole_delete(a,b,c) text_hole_delete(a,b)
  366. #endif
  367. static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
  368. // might reallocate text[]! use p += text_hole_make(p, ...),
  369. // and be careful to not use pointers into potentially freed text[]!
  370. static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
  371. #if !ENABLE_FEATURE_VI_UNDO
  372. #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
  373. #endif
  374. static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
  375. static void show_help(void); // display some help info
  376. static void rawmode(void); // set "raw" mode on tty
  377. static void cookmode(void); // return to "cooked" mode on tty
  378. // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
  379. static int mysleep(int);
  380. static int readit(void);
  381. #if ENABLE_FEATURE_VI_DOT_CMD
  382. static int get_one_char(void); // read 1 char from stdin
  383. #else
  384. # define get_one_char() readit()
  385. #endif
  386. static int get_motion_char(void); // Get type of thing to operate on and adjust count
  387. static int file_insert(const char *, char *, int); // file_insert might reallocate text[]!
  388. static int file_write(char *, char *, char *);
  389. static void screen_erase(void);
  390. static void go_bottom_and_clear_to_eol(void);
  391. static void standout_start(void); // send "start reverse video" sequence
  392. static void standout_end(void); // send "end reverse video" sequence
  393. static void flash(int); // flash the terminal screen
  394. static void show_status_line(void); // put a message on the bottom line
  395. static void status_line(const char *, ...); // print to status buf
  396. static void status_line_bold(const char *, ...);
  397. static void status_line_bold_errno(const char *fn);
  398. static void not_implemented(const char *); // display "Not implemented" message
  399. static int format_edit_status(void); // format file status on status line
  400. static void redraw(int); // force a full screen refresh
  401. static char *format_line(char* /*, int*/);
  402. static void refresh(int); // update the terminal from screen[]
  403. static void indicate_error(void); // use flash or beep to indicate error
  404. static void Hit_Return(void);
  405. #if ENABLE_FEATURE_VI_SEARCH
  406. static char *char_search(char *, const char *, int); // search for pattern starting at p
  407. #endif
  408. #if ENABLE_FEATURE_VI_COLON
  409. static char *get_address(char *p, int *b, int *e, unsigned int *got); // get colon addr, if present
  410. #endif
  411. static void colon(char *); // execute the "colon" mode cmds
  412. #if ENABLE_FEATURE_VI_DOT_CMD
  413. static void start_new_cmd_q(char); // new queue for command
  414. static void end_cmd_q(void); // stop saving input chars
  415. #else
  416. #define end_cmd_q() ((void)0)
  417. #endif
  418. #if ENABLE_FEATURE_VI_SETOPTS
  419. static void showmatching(char *); // show the matching pair () [] {}
  420. #endif
  421. #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH)
  422. // might reallocate text[]! use p += string_insert(p, ...),
  423. // and be careful to not use pointers into potentially freed text[]!
  424. # if !ENABLE_FEATURE_VI_UNDO
  425. #define string_insert(a,b,c) string_insert(a,b)
  426. # endif
  427. static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
  428. #endif
  429. #if ENABLE_FEATURE_VI_YANKMARK
  430. static char *text_yank(char *, char *, int, int); // save copy of "p" into a register
  431. static char what_reg(void); // what is letter of current YDreg
  432. static void check_context(char); // remember context for '' command
  433. #endif
  434. #if ENABLE_FEATURE_VI_UNDO
  435. static void flush_undo_data(void);
  436. static void undo_push(char *, unsigned, int); // push an operation on the undo stack
  437. static void undo_push_insert(char *, int, int); // convenience function
  438. static void undo_pop(void); // undo the last operation
  439. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  440. static void undo_queue_commit(void); // flush any queued objects to the undo stack
  441. # else
  442. # define undo_queue_commit() ((void)0)
  443. # endif
  444. #else
  445. #define flush_undo_data() ((void)0)
  446. #define undo_queue_commit() ((void)0)
  447. #endif
  448. static struct optparse options;
  449. static int vi_main(int argc, char **argv)
  450. {
  451. int c;
  452. char *file_name;
  453. if(vi_mem_init() == 0)
  454. {
  455. LOG_E("vi initialization failed.\r\n");
  456. return -1;
  457. }
  458. INIT_G();
  459. #if ENABLE_FEATURE_VI_UNDO
  460. /* undo_stack_tail = NULL; - already is */
  461. #if ENABLE_FEATURE_VI_UNDO_QUEUE
  462. undo_queue_state = UNDO_EMPTY;
  463. /* undo_q = 0; - already is */
  464. #endif
  465. #endif
  466. #ifdef NO_SUCH_APPLET_YET
  467. // if we aren't "vi", we are "view"
  468. if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
  469. SET_READONLY_MODE(readonly_mode);
  470. }
  471. #endif
  472. // 0: all of our options are disabled by default in vim
  473. //vi_setops = 0;
  474. // 1- process EXINIT variable from environment
  475. // 2- if EXINIT is unset process $HOME/.exrc file (not inplemented yet)
  476. // 3- process command line args
  477. #if ENABLE_FEATURE_VI_COLON
  478. {
  479. char *p = getenv("EXINIT");
  480. if (p && *p)
  481. initial_cmds[0] = vi_strndup(p, MAX_INPUT_LEN);
  482. }
  483. #endif
  484. optparse_init(&options, argc, argv);
  485. while ((c = optparse(&options,
  486. "RHh" IF_FEATURE_VI_COLON("c:"))) != -1) {
  487. switch (c) {
  488. #if ENABLE_FEATURE_VI_READONLY
  489. case 'R': // Read-only flag
  490. SET_READONLY_MODE(readonly_mode);
  491. break;
  492. #endif
  493. #if ENABLE_FEATURE_VI_COLON
  494. case 'c': // cmd line vi command
  495. if (*options.optarg)
  496. initial_cmds[initial_cmds[0] != NULL] = vi_strndup(options.optarg, MAX_INPUT_LEN);
  497. break;
  498. #endif
  499. case 'H':
  500. show_help();
  501. // fall through
  502. default:
  503. vi_puts(_show_usage);
  504. goto vi_exit;
  505. }
  506. }
  507. options.argv += options.optind;
  508. cmdline_filecnt = argc - options.optind;
  509. options.optind = 0;
  510. file_name = optparse_arg(&options);
  511. if(file_name == NULL)
  512. {
  513. vi_puts(_show_usage);
  514. goto vi_exit;
  515. }
  516. // "Save cursor, use alternate screen buffer, clear screen"
  517. vi_puts(ESC"[?1049h");
  518. // This is the main file handling loop
  519. while (1)
  520. {
  521. edit_file(file_name); // might be NULL on 1st iteration
  522. // NB: optind can be changed by ":next" and ":rewind" commands
  523. options.optind++;
  524. file_name = optparse_arg(&options);
  525. if (options.optind >= cmdline_filecnt)
  526. break;
  527. }
  528. // "Use normal screen buffer, restore cursor"
  529. vi_puts(ESC"[?1049l");
  530. vi_exit:
  531. vi_free(text);
  532. vi_free(screen);
  533. vi_free(current_filename);
  534. #if ENABLE_FEATURE_VI_DOT_CMD
  535. vi_free(ioq_start);
  536. #endif
  537. #if ENABLE_FEATURE_VI_SEARCH
  538. vi_free(last_search_pattern);
  539. #endif
  540. vi_free(ptr_to_globals);
  541. vi_mem_release();
  542. return 0;
  543. }
  544. MSH_CMD_EXPORT_ALIAS(vi_main, vi, a screen-oriented text editor);
  545. #if ENABLE_FEATURE_VI_COLON_EXPAND
  546. static void init_filename(char *fn)
  547. {
  548. char *copy = vi_strdup(fn);
  549. if (current_filename == NULL) {
  550. current_filename = copy;
  551. } else {
  552. vi_free(alt_filename);
  553. alt_filename = copy;
  554. }
  555. }
  556. #else
  557. # define init_filename(f) ((void)(0))
  558. #endif
  559. static void update_filename(char *fn)
  560. {
  561. #if ENABLE_FEATURE_VI_COLON_EXPAND
  562. if (fn == NULL)
  563. return;
  564. if (current_filename == NULL || strcmp(fn, current_filename) != 0) {
  565. vi_free(alt_filename);
  566. alt_filename = current_filename;
  567. current_filename = vi_strdup(fn);
  568. }
  569. #else
  570. if (fn != current_filename) {
  571. vi_free(current_filename);
  572. current_filename = vi_strdup(fn);
  573. }
  574. #endif
  575. }
  576. /* read text from file or create an empty buf */
  577. /* will also update current_filename */
  578. static int init_text_buffer(char *fn)
  579. {
  580. int rc;
  581. flush_undo_data();
  582. modified_count = 0;
  583. last_modified_count = -1;
  584. #if ENABLE_FEATURE_VI_YANKMARK
  585. /* init the marks */
  586. rt_memset(mark, 0, sizeof(mark));
  587. #endif
  588. /* allocate/reallocate text buffer */
  589. vi_free(text);
  590. text_size = 10240;
  591. screenbegin = dot = end = text = vi_zalloc(text_size);
  592. update_filename(fn);
  593. rc = file_insert(fn, text, 1);
  594. if (rc < 0) {
  595. // file doesnt exist. Start empty buf with dummy line
  596. char_insert(text, '\n', NO_UNDO);
  597. }
  598. return rc;
  599. }
  600. #if ENABLE_FEATURE_VI_WIN_RESIZE
  601. static int query_screen_dimensions(void)
  602. {
  603. int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
  604. if (rows > MAX_SCR_ROWS)
  605. rows = MAX_SCR_ROWS;
  606. if (columns > MAX_SCR_COLS)
  607. columns = MAX_SCR_COLS;
  608. return err;
  609. }
  610. #else
  611. static int query_screen_dimensions(void) { return 0; }
  612. #endif
  613. static void edit_file(char *fn)
  614. {
  615. #if ENABLE_FEATURE_VI_YANKMARK
  616. #define cur_line edit_file__cur_line
  617. #endif
  618. int c;
  619. editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
  620. rawmode();
  621. rows = 24;
  622. columns = 80;
  623. IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
  624. #if ENABLE_FEATURE_VI_ASK_TERMINAL
  625. if (G.get_rowcol_error /* TODO? && no input on stdin */) {
  626. uint64_t k;
  627. vi_puts(ESC"[999;999H" ESC"[6n");
  628. k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
  629. if ((int32_t)k == KEYCODE_CURSOR_POS) {
  630. uint32_t rc = (k >> 32);
  631. columns = (rc & 0x7fff);
  632. if (columns > MAX_SCR_COLS)
  633. columns = MAX_SCR_COLS;
  634. rows = ((rc >> 16) & 0x7fff);
  635. if (rows > MAX_SCR_ROWS)
  636. rows = MAX_SCR_ROWS;
  637. }
  638. }
  639. #endif
  640. new_screen(rows, columns); // get memory for virtual screen
  641. init_text_buffer(fn);
  642. #if ENABLE_FEATURE_VI_YANKMARK
  643. YDreg = 26; // default Yank/Delete reg
  644. // Ureg = 27; - const // hold orig line for "U" cmd
  645. mark[26] = mark[27] = text; // init "previous context"
  646. #endif
  647. crow = 0;
  648. ccol = 0;
  649. cmd_mode = 0; // 0=command 1=insert 2='R'eplace
  650. cmdcnt = 0;
  651. offset = 0; // no horizontal offset
  652. c = '\0';
  653. #if ENABLE_FEATURE_VI_DOT_CMD
  654. vi_free(ioq_start);
  655. ioq_start = NULL;
  656. adding2q = 0;
  657. #endif
  658. #if ENABLE_FEATURE_VI_COLON
  659. {
  660. char *p, *q;
  661. int n = 0;
  662. while ((p = initial_cmds[n]) != NULL) {
  663. do {
  664. q = p;
  665. p = strchr(q, '\n');
  666. if (p)
  667. while (*p == '\n')
  668. *p++ = '\0';
  669. if (*q)
  670. colon(q);
  671. } while (p);
  672. vi_free(initial_cmds[n]);
  673. initial_cmds[n] = NULL;
  674. n++;
  675. }
  676. }
  677. #endif
  678. redraw(FALSE); // dont force every col re-draw
  679. //------This is the main Vi cmd handling loop -----------------------
  680. while (editing > 0) {
  681. c = get_one_char(); // get a cmd from user
  682. #if ENABLE_FEATURE_VI_YANKMARK
  683. // save a copy of the current line- for the 'U" command
  684. if (begin_line(dot) != cur_line) {
  685. cur_line = begin_line(dot);
  686. text_yank(begin_line(dot), end_line(dot), Ureg, PARTIAL);
  687. }
  688. #endif
  689. #if ENABLE_FEATURE_VI_DOT_CMD
  690. // If c is a command that changes text[],
  691. // (re)start remembering the input for the "." command.
  692. if (!adding2q
  693. && ioq_start == NULL
  694. && cmd_mode == 0 // command mode
  695. && c > '\0' // exclude NUL and non-ASCII chars
  696. && c < 0x7f // (Unicode and such)
  697. && strchr(modifying_cmds, c)
  698. ) {
  699. start_new_cmd_q(c);
  700. }
  701. #endif
  702. do_cmd(c); // execute the user command
  703. // poll to see if there is input already waiting. if we are
  704. // not able to display output fast enough to keep up, skip
  705. // the display update until we catch up with input.
  706. if (!readbuffer[0] && mysleep(0) == 0) {
  707. // no input pending - so update output
  708. refresh(FALSE);
  709. show_status_line();
  710. }
  711. }
  712. //-------------------------------------------------------------------
  713. go_bottom_and_clear_to_eol();
  714. cookmode();
  715. #undef cur_line
  716. }
  717. //----- The Colon commands -------------------------------------
  718. #if ENABLE_FEATURE_VI_COLON
  719. // Evaluate colon address expression. Returns a pointer to the
  720. // next character or NULL on error. If 'result' contains a valid
  721. // address 'valid' is TRUE.
  722. static char *get_one_address(char *p, int *result, int *valid)
  723. {
  724. int num, sign, addr, got_addr;
  725. # if ENABLE_FEATURE_VI_YANKMARK || ENABLE_FEATURE_VI_SEARCH
  726. char *q, c;
  727. # endif
  728. IF_FEATURE_VI_SEARCH(int dir;)
  729. got_addr = FALSE;
  730. addr = count_lines(text, dot); // default to current line
  731. sign = 0;
  732. for (;;) {
  733. if (isblank(*p)) {
  734. if (got_addr) {
  735. addr += sign;
  736. sign = 0;
  737. }
  738. p++;
  739. } else if (!got_addr && *p == '.') { // the current line
  740. p++;
  741. //addr = count_lines(text, dot);
  742. got_addr = TRUE;
  743. } else if (!got_addr && *p == '$') { // the last line in file
  744. p++;
  745. addr = count_lines(text, end - 1);
  746. got_addr = TRUE;
  747. }
  748. # if ENABLE_FEATURE_VI_YANKMARK
  749. else if (!got_addr && *p == '\'') { // is this a mark addr
  750. p++;
  751. c = tolower(*p);
  752. p++;
  753. q = NULL;
  754. if (c >= 'a' && c <= 'z') {
  755. // we have a mark
  756. c = c - 'a';
  757. q = mark[(unsigned char) c];
  758. }
  759. if (q == NULL) { // is mark valid
  760. status_line_bold("Mark not set");
  761. return NULL;
  762. }
  763. addr = count_lines(text, q);
  764. got_addr = TRUE;
  765. }
  766. # endif
  767. # if ENABLE_FEATURE_VI_SEARCH
  768. else if (!got_addr && (*p == '/' || *p == '?')) { // a search pattern
  769. c = *p;
  770. q = strchrnul(p + 1, c);
  771. if (p + 1 != q) {
  772. // save copy of new pattern
  773. vi_free(last_search_pattern);
  774. last_search_pattern = vi_strndup(p, q - p);
  775. }
  776. p = q;
  777. if (*p == c)
  778. p++;
  779. if (c == '/') {
  780. q = next_line(dot);
  781. dir = (FORWARD << 1) | FULL;
  782. } else {
  783. q = begin_line(dot);
  784. dir = ((unsigned)BACK << 1) | FULL;
  785. }
  786. q = char_search(q, last_search_pattern + 1, dir);
  787. if (q == NULL) {
  788. // no match, continue from other end of file
  789. q = char_search(dir > 0 ? text : end - 1,
  790. last_search_pattern + 1, dir);
  791. if (q == NULL) {
  792. status_line_bold("Pattern not found");
  793. return NULL;
  794. }
  795. }
  796. addr = count_lines(text, q);
  797. got_addr = TRUE;
  798. }
  799. # endif
  800. else if (isdigit(*p)) {
  801. num = 0;
  802. while (isdigit(*p))
  803. num = num * 10 + *p++ -'0';
  804. if (!got_addr) { // specific line number
  805. addr = num;
  806. got_addr = TRUE;
  807. } else { // offset from current addr
  808. addr += sign >= 0 ? num : -num;
  809. }
  810. sign = 0;
  811. } else if (*p == '-' || *p == '+') {
  812. if (!got_addr) { // default address is dot
  813. //addr = count_lines(text, dot);
  814. got_addr = TRUE;
  815. } else {
  816. addr += sign;
  817. }
  818. sign = *p++ == '-' ? -1 : 1;
  819. } else {
  820. addr += sign; // consume unused trailing sign
  821. break;
  822. }
  823. }
  824. *result = addr;
  825. *valid = got_addr;
  826. return p;
  827. }
  828. # define GET_ADDRESS 0
  829. # define GET_SEPARATOR 1
  830. // Read line addresses for a colon command. The user can enter as
  831. // many as they like but only the last two will be used.
  832. static char *get_address(char *p, int *b, int *e, unsigned int *got)
  833. {
  834. int state = GET_ADDRESS;
  835. int valid;
  836. int addr;
  837. char *save_dot = dot;
  838. //----- get the address' i.e., 1,3 'a,'b -----
  839. for (;;) {
  840. if (isblank(*p)) {
  841. p++;
  842. } else if (state == GET_ADDRESS && *p == '%') { // alias for 1,$
  843. p++;
  844. *b = 1;
  845. *e = count_lines(text, end-1);
  846. *got = 3;
  847. state = GET_SEPARATOR;
  848. } else if (state == GET_ADDRESS) {
  849. valid = FALSE;
  850. p = get_one_address(p, &addr, &valid);
  851. // Quit on error or if the address is invalid and isn't of
  852. // the form ',$' or '1,' (in which case it defaults to dot).
  853. if (p == NULL || !(valid || *p == ',' || *p == ';' || *got & 1))
  854. break;
  855. *b = *e;
  856. *e = addr;
  857. *got = (*got << 1) | 1;
  858. state = GET_SEPARATOR;
  859. } else if (state == GET_SEPARATOR && (*p == ',' || *p == ';')) {
  860. if (*p == ';')
  861. dot = find_line(*e);
  862. p++;
  863. state = GET_ADDRESS;
  864. } else {
  865. break;
  866. }
  867. }
  868. dot = save_dot;
  869. return p;
  870. }
  871. # if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
  872. static void setops(char *args, int flg_no)
  873. {
  874. char *eq;
  875. int index;
  876. eq = strchr(args, '=');
  877. if (eq) *eq = '\0';
  878. index = index_in_strings(OPTS_STR, args + flg_no);
  879. if (eq) *eq = '=';
  880. if (index < 0) {
  881. bad:
  882. status_line_bold("bad option: %s", args);
  883. return;
  884. }
  885. index = 1 << (index >> 1); // convert to VI_bit
  886. if (index & VI_TABSTOP) {
  887. int t;
  888. if (!eq || flg_no) // no "=NNN" or it is "notabstop"?
  889. goto bad;
  890. t = vi_strtou(eq + 1, NULL, 10);
  891. if (t <= 0 || t > MAX_TABSTOP)
  892. goto bad;
  893. tabstop = t;
  894. return;
  895. }
  896. if (eq) goto bad; // boolean option has "="?
  897. if (flg_no) {
  898. vi_setops &= ~index;
  899. } else {
  900. vi_setops |= index;
  901. }
  902. }
  903. # endif
  904. # if ENABLE_FEATURE_VI_COLON_EXPAND
  905. static char *expand_args(char *args)
  906. {
  907. char *s, *t;
  908. const char *replace;
  909. args = vi_strdup(args);
  910. for (s = args; *s; s++) {
  911. if (*s == '%') {
  912. replace = current_filename;
  913. } else if (*s == '#') {
  914. replace = alt_filename;
  915. } else {
  916. if (*s == '\\' && s[1] != '\0') {
  917. for (t = s++; *t; t++)
  918. *t = t[1];
  919. }
  920. continue;
  921. }
  922. if (replace == NULL) {
  923. vi_free(args);
  924. status_line_bold("No previous filename");
  925. return NULL;
  926. }
  927. *s = '\0';
  928. t = xasprintf("%s%s%s", args, replace, s+1);
  929. s = t + (s - args) + strlen(replace);
  930. vi_free(args);
  931. args = t;
  932. }
  933. return args;
  934. }
  935. # else
  936. # define expand_args(a) (a)
  937. # endif
  938. #endif /* FEATURE_VI_COLON */
  939. # define strchr_backslash(s, c) strchr(s, c)
  940. // buf must be no longer than MAX_INPUT_LEN!
  941. static void colon(char *buf)
  942. {
  943. #if !ENABLE_FEATURE_VI_COLON
  944. /* Simple ":cmd" handler with minimal set of commands */
  945. char *p = buf;
  946. int cnt;
  947. if (*p == ':')
  948. p++;
  949. cnt = strlen(p);
  950. if (cnt == 0)
  951. return;
  952. if (strncmp(p, "quit", cnt) == 0
  953. || strcmp(p, "q!") == 0
  954. ) {
  955. if (modified_count && p[1] != '!') {
  956. status_line_bold("No write since last change (:%s! overrides)", p);
  957. } else {
  958. editing = 0;
  959. }
  960. return;
  961. }
  962. if (strncmp(p, "write", cnt) == 0
  963. || strcmp(p, "wq") == 0
  964. || strcmp(p, "wn") == 0
  965. || (p[0] == 'x' && !p[1])
  966. ) {
  967. if (modified_count != 0 || p[0] != 'x') {
  968. cnt = file_write(current_filename, text, end - 1);
  969. }
  970. if (cnt < 0) {
  971. if (cnt == -1)
  972. status_line_bold("Write error: %s", strerror(errno));
  973. } else {
  974. modified_count = 0;
  975. last_modified_count = -1;
  976. status_line("'%s' %uL, %uC",
  977. current_filename,
  978. count_lines(text, end - 1), cnt
  979. );
  980. if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
  981. || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
  982. ) {
  983. editing = 0;
  984. }
  985. }
  986. return;
  987. }
  988. if (strncmp(p, "file", cnt) == 0) {
  989. last_status_cksum = 0; // force status update
  990. return;
  991. }
  992. if (sscanf(p, "%d", &cnt) > 0) {
  993. dot = find_line(cnt);
  994. dot_skip_over_ws();
  995. return;
  996. }
  997. not_implemented(p);
  998. #else
  999. // check how many addresses we got
  1000. # define GOT_ADDRESS (got & 1)
  1001. # define GOT_RANGE ((got & 3) == 3)
  1002. char c, *buf1, *q, *r;
  1003. char *fn, cmd[MAX_INPUT_LEN], *cmdend, *args, *exp = NULL;
  1004. int i, l, li, b, e;
  1005. unsigned int got;
  1006. int useforce;
  1007. // :3154 // if (-e line 3154) goto it else stay put
  1008. // :4,33w! foo // write a portion of buffer to file "foo"
  1009. // :w // write all of buffer to current file
  1010. // :q // quit
  1011. // :q! // quit- dont care about modified file
  1012. // :'a,'z!sort -u // filter block through sort
  1013. // :'f // goto mark "f"
  1014. // :'fl // list literal the mark "f" line
  1015. // :.r bar // read file "bar" into buffer before dot
  1016. // :/123/,/abc/d // delete lines from "123" line to "abc" line
  1017. // :/xyz/ // goto the "xyz" line
  1018. // :s/find/replace/ // substitute pattern "find" with "replace"
  1019. // :!<cmd> // run <cmd> then return
  1020. //
  1021. while (*buf == ':')
  1022. buf++; // move past leading colons
  1023. while (isblank(*buf))
  1024. buf++; // move past leading blanks
  1025. if (!buf[0] || buf[0] == '"')
  1026. goto ret; // ignore empty lines or those starting with '"'
  1027. li = i = 0;
  1028. b = e = -1;
  1029. got = 0;
  1030. li = count_lines(text, end - 1);
  1031. fn = current_filename;
  1032. // look for optional address(es) :. :1 :1,9 :'q,'a :%
  1033. buf = get_address(buf, &b, &e, &got);
  1034. if (buf == NULL) {
  1035. goto ret;
  1036. }
  1037. // get the COMMAND into cmd[]
  1038. strcpy(cmd, buf);
  1039. buf1 = cmd;
  1040. while (!isspace(*buf1) && *buf1 != '\0') {
  1041. buf1++;
  1042. }
  1043. cmdend = buf1;
  1044. // get any ARGuments
  1045. while (isblank(*buf1))
  1046. buf1++;
  1047. args = buf1;
  1048. *cmdend = '\0';
  1049. useforce = FALSE;
  1050. if (cmdend > cmd && cmdend[-1] == '!') {
  1051. useforce = TRUE;
  1052. cmdend[-1] = '\0'; // get rid of !
  1053. }
  1054. // assume the command will want a range, certain commands
  1055. // (read, substitute) need to adjust these assumptions
  1056. if (!GOT_ADDRESS) {
  1057. q = text; // no addr, use 1,$ for the range
  1058. r = end - 1;
  1059. } else {
  1060. // at least one addr was given, get its details
  1061. if (e < 0 || e > li) {
  1062. status_line_bold("Invalid range");
  1063. goto ret;
  1064. }
  1065. q = r = find_line(e);
  1066. if (!GOT_RANGE) {
  1067. // if there is only one addr, then it's the line
  1068. // number of the single line the user wants.
  1069. // Reset the end pointer to the end of that line.
  1070. r = end_line(q);
  1071. li = 1;
  1072. } else {
  1073. // we were given two addrs. change the
  1074. // start pointer to the addr given by user.
  1075. if (b < 0 || b > li || b > e) {
  1076. status_line_bold("Invalid range");
  1077. goto ret;
  1078. }
  1079. q = find_line(b); // what line is #b
  1080. r = end_line(r);
  1081. li = e - b + 1;
  1082. }
  1083. }
  1084. // ------------ now look for the command ------------
  1085. i = strlen(cmd);
  1086. if (i == 0) { // :123CR goto line #123
  1087. if (e >= 0) {
  1088. dot = find_line(e); // what line is #e
  1089. dot_skip_over_ws();
  1090. }
  1091. }
  1092. #if ENABLE_FEATURE_ALLOW_EXEC
  1093. else if (cmd[0] == '!') { // run a cmd
  1094. int retcode;
  1095. // :!ls run the <cmd>
  1096. exp = expand_args(buf + 1);
  1097. if (exp == NULL)
  1098. goto ret;
  1099. go_bottom_and_clear_to_eol();
  1100. cookmode();
  1101. retcode = system(exp); // run the cmd
  1102. if (retcode)
  1103. vi_puts("\nshell returned %i\n\n", retcode);
  1104. rawmode();
  1105. Hit_Return(); // let user see results
  1106. }
  1107. #endif
  1108. else if (cmd[0] == '=' && !cmd[1]) { // where is the address
  1109. if (!GOT_ADDRESS) { // no addr given- use defaults
  1110. e = count_lines(text, dot);
  1111. }
  1112. status_line("%d", e);
  1113. } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
  1114. if (!GOT_ADDRESS) { // no addr given- use defaults
  1115. q = begin_line(dot); // assume .,. for the range
  1116. r = end_line(dot);
  1117. }
  1118. dot = yank_delete(q, r, WHOLE, YANKDEL, ALLOW_UNDO); // save, then delete lines
  1119. dot_skip_over_ws();
  1120. } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
  1121. int size;
  1122. // don't edit, if the current file has been modified
  1123. if (modified_count && !useforce) {
  1124. status_line_bold("No write since last change (:%s! overrides)", cmd);
  1125. goto ret;
  1126. }
  1127. if (args[0]) {
  1128. // the user supplied a file name
  1129. fn = exp = expand_args(args);
  1130. if (exp == NULL)
  1131. goto ret;
  1132. } else if (current_filename == NULL) {
  1133. // no user file name, no current name- punt
  1134. status_line_bold("No current filename");
  1135. goto ret;
  1136. }
  1137. size = init_text_buffer(fn);
  1138. #if ENABLE_FEATURE_VI_YANKMARK
  1139. if (Ureg >= 0 && Ureg < 28) {
  1140. vi_free(reg[Ureg]); // free orig line reg- for 'U'
  1141. reg[Ureg] = NULL;
  1142. }
  1143. /*if (YDreg < 28) - always true*/ {
  1144. vi_free(reg[YDreg]); // free default yank/delete register
  1145. reg[YDreg] = NULL;
  1146. }
  1147. #endif
  1148. // how many lines in text[]?
  1149. li = count_lines(text, end - 1);
  1150. status_line("'%s'%s"
  1151. IF_FEATURE_VI_READONLY("%s")
  1152. " %uL, %uC",
  1153. fn,
  1154. (size < 0 ? " [New file]" : ""),
  1155. IF_FEATURE_VI_READONLY(
  1156. ((readonly_mode) ? " [Readonly]" : ""),
  1157. )
  1158. li, (int)(end - text)
  1159. );
  1160. } else if (strncmp(cmd, "file", i) == 0) { // what File is this
  1161. if (e >= 0) {
  1162. status_line_bold("No address allowed on this command");
  1163. goto ret;
  1164. }
  1165. if (args[0]) {
  1166. // user wants a new filename
  1167. exp = expand_args(args);
  1168. if (exp == NULL)
  1169. goto ret;
  1170. update_filename(exp);
  1171. } else {
  1172. // user wants file status info
  1173. last_status_cksum = 0; // force status update
  1174. }
  1175. } else if (strncmp(cmd, "features", i) == 0) { // what features are available
  1176. // print out values of all features
  1177. go_bottom_and_clear_to_eol();
  1178. cookmode();
  1179. show_help();
  1180. rawmode();
  1181. Hit_Return();
  1182. } else if (strncmp(cmd, "list", i) == 0) { // literal print line
  1183. if (!GOT_ADDRESS) { // no addr given- use defaults
  1184. q = begin_line(dot); // assume .,. for the range
  1185. r = end_line(dot);
  1186. }
  1187. go_bottom_and_clear_to_eol();
  1188. vi_puts("\r");
  1189. for (; q <= r; q++) {
  1190. int c_is_no_print;
  1191. c = *q;
  1192. c_is_no_print = (c & 0x80) && !Isprint(c);
  1193. if (c_is_no_print) {
  1194. c = '.';
  1195. standout_start();
  1196. }
  1197. if (c == '\n') {
  1198. vi_puts("$\r");
  1199. } else if (c < ' ' || c == 127) {
  1200. vi_putchar('^');
  1201. if (c == 127)
  1202. c = '?';
  1203. else
  1204. c += '@';
  1205. }
  1206. vi_putchar(c);
  1207. if (c_is_no_print)
  1208. standout_end();
  1209. }
  1210. Hit_Return();
  1211. } else if (strncmp(cmd, "quit", i) == 0 // quit
  1212. || strncmp(cmd, "next", i) == 0 // edit next file
  1213. || strncmp(cmd, "prev", i) == 0 // edit previous file
  1214. ) {
  1215. int n;
  1216. if (useforce) {
  1217. if (*cmd == 'q') {
  1218. // force end of argv list
  1219. options.optind = cmdline_filecnt;
  1220. }
  1221. editing = 0;
  1222. goto ret;
  1223. }
  1224. // don't exit if the file been modified
  1225. if (modified_count) {
  1226. status_line_bold("No write since last change (:%s! overrides)", cmd);
  1227. goto ret;
  1228. }
  1229. // are there other file to edit
  1230. n = cmdline_filecnt - options.optind - 1;
  1231. if (*cmd == 'q' && n > 0) {
  1232. status_line_bold("%u more file(s) to edit", n);
  1233. goto ret;
  1234. }
  1235. if (*cmd == 'n' && n <= 0) {
  1236. status_line_bold("No more files to edit");
  1237. goto ret;
  1238. }
  1239. if (*cmd == 'p') {
  1240. // are there previous files to edit
  1241. if (options.optind < 1) {
  1242. status_line_bold("No previous files to edit");
  1243. goto ret;
  1244. }
  1245. options.optind -= 2;
  1246. }
  1247. editing = 0;
  1248. } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
  1249. int size, num;
  1250. if (args[0]) {
  1251. // the user supplied a file name
  1252. fn = exp = expand_args(args);
  1253. if (exp == NULL)
  1254. goto ret;
  1255. init_filename(fn);
  1256. } else if (current_filename == NULL) {
  1257. // no user file name, no current name- punt
  1258. status_line_bold("No current filename");
  1259. goto ret;
  1260. }
  1261. if (e == 0) { // user said ":0r foo"
  1262. q = text;
  1263. } else { // read after given line or current line if none given
  1264. q = next_line(GOT_ADDRESS ? find_line(e) : dot);
  1265. // read after last line
  1266. if (q == end-1)
  1267. ++q;
  1268. }
  1269. num = count_lines(text, q);
  1270. if (q == end)
  1271. num++;
  1272. { // dance around potentially-reallocated text[]
  1273. uintptr_t ofs = q - text;
  1274. size = file_insert(fn, q, 0);
  1275. q = text + ofs;
  1276. }
  1277. if (size < 0)
  1278. goto ret; // nothing was inserted
  1279. // how many lines in text[]?
  1280. li = count_lines(q, q + size - 1);
  1281. status_line("'%s'"
  1282. IF_FEATURE_VI_READONLY("%s")
  1283. " %uL, %uC",
  1284. fn,
  1285. IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
  1286. li, size
  1287. );
  1288. dot = find_line(num);
  1289. } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
  1290. if (modified_count && !useforce) {
  1291. status_line_bold("No write since last change (:%s! overrides)", cmd);
  1292. } else {
  1293. // reset the filenames to edit
  1294. options.optind = -1; /* start from 0th file */
  1295. editing = 0;
  1296. }
  1297. #if ENABLE_FEATURE_VI_SET
  1298. } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
  1299. #if ENABLE_FEATURE_VI_SETOPTS
  1300. char *argp, *argn, oldch;
  1301. #endif
  1302. // only blank is regarded as args delimiter. What about tab '\t'?
  1303. if (!args[0] || strcmp(args, "all") == 0) {
  1304. // print out values of all options
  1305. #if ENABLE_FEATURE_VI_SETOPTS
  1306. status_line_bold(
  1307. "%sautoindent "
  1308. "%sexpandtab "
  1309. "%sflash "
  1310. "%signorecase "
  1311. "%sshowmatch "
  1312. "tabstop=%u",
  1313. autoindent ? "" : "no",
  1314. expandtab ? "" : "no",
  1315. err_method ? "" : "no",
  1316. ignorecase ? "" : "no",
  1317. showmatch ? "" : "no",
  1318. tabstop
  1319. );
  1320. #endif
  1321. goto ret;
  1322. }
  1323. #if ENABLE_FEATURE_VI_SETOPTS
  1324. argp = args;
  1325. while (*argp) {
  1326. i = 0;
  1327. if (argp[0] == 'n' && argp[1] == 'o') // "noXXX"
  1328. i = 2;
  1329. argn = skip_non_whitespace(argp);
  1330. oldch = *argn;
  1331. *argn = '\0';
  1332. setops(argp, i);
  1333. *argn = oldch;
  1334. argp = skip_whitespace(argn);
  1335. }
  1336. #endif /* FEATURE_VI_SETOPTS */
  1337. #endif /* FEATURE_VI_SET */
  1338. #if ENABLE_FEATURE_VI_SEARCH
  1339. } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
  1340. char *F, *R, *flags;
  1341. size_t len_F, len_R;
  1342. int gflag = 0; // global replace flag
  1343. int subs = 0; // number of substitutions
  1344. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  1345. int last_line = 0, lines = 0;
  1346. # endif
  1347. // F points to the "find" pattern
  1348. // R points to the "replace" pattern
  1349. // replace the cmd line delimiters "/" with NULs
  1350. c = buf[1]; // what is the delimiter
  1351. F = buf + 2; // start of "find"
  1352. R = strchr_backslash(F, c); // middle delimiter
  1353. if (!R)
  1354. goto colon_s_fail;
  1355. len_F = R - F;
  1356. *R++ = '\0'; // terminate "find"
  1357. flags = strchr_backslash(R, c);
  1358. if (flags) {
  1359. *flags++ = '\0'; // terminate "replace"
  1360. gflag = *flags;
  1361. }
  1362. if (len_F) { // save "find" as last search pattern
  1363. vi_free(last_search_pattern);
  1364. last_search_pattern = vi_strdup(F - 1);
  1365. last_search_pattern[0] = '/';
  1366. } else if (last_search_pattern[1] == '\0') {
  1367. status_line_bold("No previous search");
  1368. goto ret;
  1369. } else {
  1370. F = last_search_pattern + 1;
  1371. len_F = strlen(F);
  1372. }
  1373. if (!GOT_ADDRESS) { // no addr given
  1374. q = begin_line(dot); // start with cur line
  1375. r = end_line(dot);
  1376. b = e = count_lines(text, q); // cur line number
  1377. } else if (!GOT_RANGE) { // one addr given
  1378. b = e;
  1379. }
  1380. len_R = strlen(R);
  1381. for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
  1382. char *ls = q; // orig line start
  1383. char *found;
  1384. vc4:
  1385. found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
  1386. if (found) {
  1387. uintptr_t bias;
  1388. // we found the "find" pattern - delete it
  1389. // For undo support, the first item should not be chained
  1390. // This needs to be handled differently depending on
  1391. // whether or not regex support is enabled.
  1392. # define TEST_LEN_F 1 // len_F is never zero
  1393. # define TEST_UNDO1 subs
  1394. # define TEST_UNDO2 1
  1395. if (TEST_LEN_F) // match can be empty, no delete needed
  1396. text_hole_delete(found, found + len_F - 1,
  1397. TEST_UNDO1 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
  1398. if (len_R != 0) { // insert the "replace" pattern, if required
  1399. bias = string_insert(found, R,
  1400. TEST_UNDO2 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
  1401. found += bias;
  1402. ls += bias;
  1403. //q += bias; - recalculated anyway
  1404. }
  1405. if (TEST_LEN_F || len_R != 0) {
  1406. dot = ls;
  1407. subs++;
  1408. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  1409. if (last_line != i) {
  1410. last_line = i;
  1411. ++lines;
  1412. }
  1413. # endif
  1414. }
  1415. // check for "global" :s/foo/bar/g
  1416. if (gflag == 'g') {
  1417. if ((found + len_R) < end_line(ls)) {
  1418. q = found + len_R;
  1419. goto vc4; // don't let q move past cur line
  1420. }
  1421. }
  1422. }
  1423. q = next_line(ls);
  1424. }
  1425. if (subs == 0) {
  1426. status_line_bold("No match");
  1427. } else {
  1428. dot_skip_over_ws();
  1429. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  1430. if (subs > 1)
  1431. status_line("%d substitutions on %d lines", subs, lines);
  1432. # endif
  1433. }
  1434. #endif /* FEATURE_VI_SEARCH */
  1435. } else if (strncmp(cmd, "version", i) == 0) { // show software version
  1436. status_line(BB_VER " " BB_BT);
  1437. } else if (strncmp(cmd, "write", i) == 0 // write text to file
  1438. || strcmp(cmd, "wq") == 0
  1439. || strcmp(cmd, "wn") == 0
  1440. || (cmd[0] == 'x' && !cmd[1])
  1441. ) {
  1442. int size;
  1443. //int forced = FALSE;
  1444. // is there a file name to write to?
  1445. if (args[0]) {
  1446. struct stat statbuf;
  1447. exp = expand_args(args);
  1448. if (exp == NULL)
  1449. goto ret;
  1450. if (!useforce && (fn == NULL || strcmp(fn, exp) != 0) &&
  1451. stat(exp, &statbuf) == 0) {
  1452. status_line_bold("File exists (:w! overrides)");
  1453. goto ret;
  1454. }
  1455. fn = exp;
  1456. init_filename(fn);
  1457. }
  1458. # if ENABLE_FEATURE_VI_READONLY
  1459. else if (readonly_mode && !useforce && fn) {
  1460. status_line_bold("'%s' is read only", fn);
  1461. goto ret;
  1462. }
  1463. # endif
  1464. //if (useforce) {
  1465. // if "fn" is not write-able, chmod u+w
  1466. // rt_sprintf(syscmd, "chmod u+w %s", fn);
  1467. // system(syscmd);
  1468. // forced = TRUE;
  1469. //}
  1470. if (modified_count != 0 || cmd[0] != 'x') {
  1471. size = r - q + 1;
  1472. l = file_write(fn, q, r);
  1473. } else {
  1474. size = 0;
  1475. l = 0;
  1476. }
  1477. //if (useforce && forced) {
  1478. // chmod u-w
  1479. // rt_sprintf(syscmd, "chmod u-w %s", fn);
  1480. // system(syscmd);
  1481. // forced = FALSE;
  1482. //}
  1483. if (l < 0) {
  1484. if (l == -1)
  1485. status_line_bold_errno(fn);
  1486. } else {
  1487. // how many lines written
  1488. li = count_lines(q, q + l - 1);
  1489. status_line("'%s' %uL, %uC", fn, li, l);
  1490. if (l == size) {
  1491. if (q == text && q + l == end) {
  1492. modified_count = 0;
  1493. last_modified_count = -1;
  1494. }
  1495. if (cmd[1] == 'n') {
  1496. editing = 0;
  1497. } else if (cmd[0] == 'x' || cmd[1] == 'q') {
  1498. // are there other files to edit?
  1499. int n = cmdline_filecnt - options.optind - 1;
  1500. if (n > 0) {
  1501. if (useforce) {
  1502. // force end of argv list
  1503. options.optind = cmdline_filecnt;
  1504. } else {
  1505. status_line_bold("%u more file(s) to edit", n);
  1506. goto ret;
  1507. }
  1508. }
  1509. editing = 0;
  1510. }
  1511. }
  1512. }
  1513. # if ENABLE_FEATURE_VI_YANKMARK
  1514. } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
  1515. if (!GOT_ADDRESS) { // no addr given- use defaults
  1516. q = begin_line(dot); // assume .,. for the range
  1517. r = end_line(dot);
  1518. }
  1519. text_yank(q, r, YDreg, WHOLE);
  1520. li = count_lines(q, r);
  1521. status_line("Yank %d lines (%d chars) into [%c]",
  1522. li, strlen(reg[YDreg]), what_reg());
  1523. # endif
  1524. } else {
  1525. // cmd unknown
  1526. not_implemented(cmd);
  1527. }
  1528. ret:
  1529. # if ENABLE_FEATURE_VI_COLON_EXPAND
  1530. vi_free(exp);
  1531. # endif
  1532. dot = bound_dot(dot); // make sure "dot" is valid
  1533. return;
  1534. # if ENABLE_FEATURE_VI_SEARCH
  1535. colon_s_fail:
  1536. status_line(":s expression missing delimiters");
  1537. # endif
  1538. #endif /* FEATURE_VI_COLON */
  1539. }
  1540. static void Hit_Return(void)
  1541. {
  1542. int c;
  1543. standout_start();
  1544. vi_puts("[Hit return to continue]");
  1545. standout_end();
  1546. while ((c = get_one_char()) != '\n' && c != '\r')
  1547. continue;
  1548. redraw(TRUE); // force redraw all
  1549. }
  1550. static int next_tabstop(int col)
  1551. {
  1552. return col + ((tabstop - 1) - (col % tabstop));
  1553. }
  1554. static int prev_tabstop(int col)
  1555. {
  1556. return col - ((col % tabstop) ? (col % tabstop) : tabstop);
  1557. }
  1558. static int next_column(char c, int co)
  1559. {
  1560. if (c == '\t')
  1561. co = next_tabstop(co);
  1562. else if ((unsigned char)c < ' ' || c == 0x7f)
  1563. co++; // display as ^X, use 2 columns
  1564. return co + 1;
  1565. }
  1566. static int get_column(char *p)
  1567. {
  1568. const char *r;
  1569. int co = 0;
  1570. for (r = begin_line(p); r < p; r++)
  1571. co = next_column(*r, co);
  1572. return co;
  1573. }
  1574. //----- Synchronize the cursor to Dot --------------------------
  1575. static void sync_cursor(char *d, int *row, int *col)
  1576. {
  1577. char *beg_cur; // begin and end of "d" line
  1578. char *tp;
  1579. int cnt, ro, co;
  1580. beg_cur = begin_line(d); // first char of cur line
  1581. if (beg_cur < screenbegin) {
  1582. // "d" is before top line on screen
  1583. // how many lines do we have to move
  1584. cnt = count_lines(beg_cur, screenbegin);
  1585. sc1:
  1586. screenbegin = beg_cur;
  1587. if (cnt > (rows - 1) / 2) {
  1588. // we moved too many lines. put "dot" in middle of screen
  1589. for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
  1590. screenbegin = prev_line(screenbegin);
  1591. }
  1592. }
  1593. } else {
  1594. char *end_scr; // begin and end of screen
  1595. end_scr = end_screen(); // last char of screen
  1596. if (beg_cur > end_scr) {
  1597. // "d" is after bottom line on screen
  1598. // how many lines do we have to move
  1599. cnt = count_lines(end_scr, beg_cur);
  1600. if (cnt > (rows - 1) / 2)
  1601. goto sc1; // too many lines
  1602. for (ro = 0; ro < cnt - 1; ro++) {
  1603. // move screen begin the same amount
  1604. screenbegin = next_line(screenbegin);
  1605. // now, move the end of screen
  1606. end_scr = next_line(end_scr);
  1607. end_scr = end_line(end_scr);
  1608. }
  1609. }
  1610. }
  1611. // "d" is on screen- find out which row
  1612. tp = screenbegin;
  1613. for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
  1614. if (tp == beg_cur)
  1615. break;
  1616. tp = next_line(tp);
  1617. }
  1618. // find out what col "d" is on
  1619. co = 0;
  1620. do { // drive "co" to correct column
  1621. if (*tp == '\n') //vda || *tp == '\0')
  1622. break;
  1623. co = next_column(*tp, co) - 1;
  1624. // inserting text before a tab, don't include its position
  1625. if (cmd_mode && tp == d - 1 && *d == '\t') {
  1626. co++;
  1627. break;
  1628. }
  1629. } while (tp++ < d && ++co);
  1630. // "co" is the column where "dot" is.
  1631. // The screen has "columns" columns.
  1632. // The currently displayed columns are 0+offset -- columns+ofset
  1633. // |-------------------------------------------------------------|
  1634. // ^ ^ ^
  1635. // offset | |------- columns ----------------|
  1636. //
  1637. // If "co" is already in this range then we do not have to adjust offset
  1638. // but, we do have to subtract the "offset" bias from "co".
  1639. // If "co" is outside this range then we have to change "offset".
  1640. // If the first char of a line is a tab the cursor will try to stay
  1641. // in column 7, but we have to set offset to 0.
  1642. if (co < 0 + offset) {
  1643. offset = co;
  1644. }
  1645. if (co >= columns + offset) {
  1646. offset = co - columns + 1;
  1647. }
  1648. // if the first char of the line is a tab, and "dot" is sitting on it
  1649. // force offset to 0.
  1650. if (d == beg_cur && *d == '\t') {
  1651. offset = 0;
  1652. }
  1653. co -= offset;
  1654. *row = ro;
  1655. *col = co;
  1656. }
  1657. //----- Text Movement Routines ---------------------------------
  1658. static char *begin_line(char *p) // return pointer to first char cur line
  1659. {
  1660. if (p > text) {
  1661. p = memrchr(text, '\n', p - text);
  1662. if (!p)
  1663. return text;
  1664. return p + 1;
  1665. }
  1666. return p;
  1667. }
  1668. static char *end_line(char *p) // return pointer to NL of cur line
  1669. {
  1670. if (p < end - 1) {
  1671. p = memchr(p, '\n', end - p - 1);
  1672. if (!p)
  1673. return end - 1;
  1674. }
  1675. return p;
  1676. }
  1677. static char *dollar_line(char *p) // return pointer to just before NL line
  1678. {
  1679. p = end_line(p);
  1680. // Try to stay off of the Newline
  1681. if (*p == '\n' && (p - begin_line(p)) > 0)
  1682. p--;
  1683. return p;
  1684. }
  1685. static char *prev_line(char *p) // return pointer first char prev line
  1686. {
  1687. p = begin_line(p); // goto begining of cur line
  1688. if (p > text && p[-1] == '\n')
  1689. p--; // step to prev line
  1690. p = begin_line(p); // goto begining of prev line
  1691. return p;
  1692. }
  1693. static char *next_line(char *p) // return pointer first char next line
  1694. {
  1695. p = end_line(p);
  1696. if (p < end - 1 && *p == '\n')
  1697. p++; // step to next line
  1698. return p;
  1699. }
  1700. //----- Text Information Routines ------------------------------
  1701. static char *end_screen(void)
  1702. {
  1703. char *q;
  1704. int cnt;
  1705. // find new bottom line
  1706. q = screenbegin;
  1707. for (cnt = 0; cnt < rows - 2; cnt++)
  1708. q = next_line(q);
  1709. q = end_line(q);
  1710. return q;
  1711. }
  1712. // count line from start to stop
  1713. static int count_lines(char *start, char *stop)
  1714. {
  1715. char *q;
  1716. int cnt;
  1717. if (stop < start) { // start and stop are backwards- reverse them
  1718. q = start;
  1719. start = stop;
  1720. stop = q;
  1721. }
  1722. cnt = 0;
  1723. stop = end_line(stop);
  1724. while (start <= stop && start <= end - 1) {
  1725. start = end_line(start);
  1726. if (*start == '\n')
  1727. cnt++;
  1728. start++;
  1729. }
  1730. return cnt;
  1731. }
  1732. static char *find_line(int li) // find begining of line #li
  1733. {
  1734. char *q;
  1735. for (q = text; li > 1; li--) {
  1736. q = next_line(q);
  1737. }
  1738. return q;
  1739. }
  1740. //----- Dot Movement Routines ----------------------------------
  1741. static void dot_left(void)
  1742. {
  1743. undo_queue_commit();
  1744. if (dot > text && dot[-1] != '\n')
  1745. dot--;
  1746. }
  1747. static void dot_right(void)
  1748. {
  1749. undo_queue_commit();
  1750. if (dot < end - 1 && *dot != '\n')
  1751. dot++;
  1752. }
  1753. static void dot_begin(void)
  1754. {
  1755. undo_queue_commit();
  1756. dot = begin_line(dot); // return pointer to first char cur line
  1757. }
  1758. static void dot_end(void)
  1759. {
  1760. undo_queue_commit();
  1761. dot = end_line(dot); // return pointer to last char cur line
  1762. }
  1763. static char *move_to_col(char *p, int l)
  1764. {
  1765. int co;
  1766. p = begin_line(p);
  1767. co = 0;
  1768. do {
  1769. if (*p == '\n') //vda || *p == '\0')
  1770. break;
  1771. co = next_column(*p, co);
  1772. } while (co <= l && p++ < end);
  1773. return p;
  1774. }
  1775. static void dot_next(void)
  1776. {
  1777. undo_queue_commit();
  1778. dot = next_line(dot);
  1779. }
  1780. static void dot_prev(void)
  1781. {
  1782. undo_queue_commit();
  1783. dot = prev_line(dot);
  1784. }
  1785. static void dot_to_char(int cmd)
  1786. {
  1787. char *q = dot;
  1788. int dir = islower(cmd) ? FORWARD : BACK;
  1789. if (last_search_char == 0)
  1790. return;
  1791. do {
  1792. do {
  1793. q += dir;
  1794. if ((dir == FORWARD ? q > end - 1 : q < text) || *q == '\n') {
  1795. indicate_error();
  1796. return;
  1797. }
  1798. } while (*q != last_search_char);
  1799. } while (--cmdcnt > 0);
  1800. dot = q;
  1801. // place cursor before/after char as required
  1802. if (cmd == 't')
  1803. dot_left();
  1804. else if (cmd == 'T')
  1805. dot_right();
  1806. }
  1807. static void dot_scroll(int cnt, int dir)
  1808. {
  1809. char *q;
  1810. undo_queue_commit();
  1811. for (; cnt > 0; cnt--) {
  1812. if (dir < 0) {
  1813. // scroll Backwards
  1814. // ctrl-Y scroll up one line
  1815. screenbegin = prev_line(screenbegin);
  1816. } else {
  1817. // scroll Forwards
  1818. // ctrl-E scroll down one line
  1819. screenbegin = next_line(screenbegin);
  1820. }
  1821. }
  1822. // make sure "dot" stays on the screen so we dont scroll off
  1823. if (dot < screenbegin)
  1824. dot = screenbegin;
  1825. q = end_screen(); // find new bottom line
  1826. if (dot > q)
  1827. dot = begin_line(q); // is dot is below bottom line?
  1828. dot_skip_over_ws();
  1829. }
  1830. static void dot_skip_over_ws(void)
  1831. {
  1832. // skip WS
  1833. while (isspace((unsigned char)*dot) && *dot != '\n' && dot < end - 1)
  1834. dot++;
  1835. }
  1836. static char *bound_dot(char *p) // make sure text[0] <= P < "end"
  1837. {
  1838. if (p >= end && end > text) {
  1839. p = end - 1;
  1840. indicate_error();
  1841. }
  1842. if (p < text) {
  1843. p = text;
  1844. indicate_error();
  1845. }
  1846. return p;
  1847. }
  1848. //----- Helper Utility Routines --------------------------------
  1849. //----------------------------------------------------------------
  1850. //----- Char Routines --------------------------------------------
  1851. /* Chars that are part of a word-
  1852. * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
  1853. * Chars that are Not part of a word (stoppers)
  1854. * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
  1855. * Chars that are WhiteSpace
  1856. * TAB NEWLINE VT FF RETURN SPACE
  1857. * DO NOT COUNT NEWLINE AS WHITESPACE
  1858. */
  1859. static void new_screen(int ro, int co)
  1860. {
  1861. char *s;
  1862. vi_free(screen);
  1863. screensize = ro * co + 8;
  1864. s = screen = vi_malloc(screensize);
  1865. // initialize the new screen. assume this will be a empty file.
  1866. screen_erase();
  1867. // non-existent text[] lines start with a tilde (~).
  1868. //screen[(1 * co) + 0] = '~';
  1869. //screen[(2 * co) + 0] = '~';
  1870. //..
  1871. //screen[((ro-2) * co) + 0] = '~';
  1872. ro -= 2;
  1873. while (--ro >= 0) {
  1874. s += co;
  1875. *s = '~';
  1876. }
  1877. }
  1878. #if ENABLE_FEATURE_VI_SEARCH
  1879. # if ENABLE_FEATURE_VI_SETOPTS
  1880. static int mycmp(const char *s1, const char *s2, int len)
  1881. {
  1882. if (ignorecase) {
  1883. return strncasecmp(s1, s2, len);
  1884. }
  1885. return strncmp(s1, s2, len);
  1886. }
  1887. # else
  1888. # define mycmp strncmp
  1889. # endif
  1890. static char *char_search(char *p, const char *pat, int dir_and_range)
  1891. {
  1892. char *start, *stop;
  1893. int len;
  1894. int range;
  1895. len = strlen(pat);
  1896. range = (dir_and_range & 1);
  1897. if (dir_and_range > 0) { //FORWARD?
  1898. stop = end - 1; // assume range is p..end-1
  1899. if (range == LIMITED)
  1900. stop = next_line(p); // range is to next line
  1901. for (start = p; start < stop; start++) {
  1902. if (mycmp(start, pat, len) == 0) {
  1903. return start;
  1904. }
  1905. }
  1906. } else { //BACK
  1907. stop = text; // assume range is text..p
  1908. if (range == LIMITED)
  1909. stop = prev_line(p); // range is to prev line
  1910. for (start = p - len; start >= stop; start--) {
  1911. if (mycmp(start, pat, len) == 0) {
  1912. return start;
  1913. }
  1914. }
  1915. }
  1916. // pattern not found
  1917. return NULL;
  1918. }
  1919. #endif /* FEATURE_VI_SEARCH */
  1920. // find number of characters in indent, p must be at beginning of line
  1921. static size_t indent_len(char *p)
  1922. {
  1923. char *r = p;
  1924. while (r < (end - 1) && isblank(*r))
  1925. r++;
  1926. return r - p;
  1927. }
  1928. static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
  1929. {
  1930. #if ENABLE_FEATURE_VI_SETOPTS
  1931. #define indentcol char_insert__indentcol
  1932. size_t len;
  1933. int col, ntab, nspc;
  1934. #endif
  1935. char *bol = begin_line(p);
  1936. if (c == 22) { // Is this an ctrl-V?
  1937. p += stupid_insert(p, '^'); // use ^ to indicate literal next
  1938. refresh(FALSE); // show the ^
  1939. c = get_one_char();
  1940. *p = c;
  1941. #if ENABLE_FEATURE_VI_UNDO
  1942. undo_push_insert(p, 1, undo);
  1943. #else
  1944. modified_count++;
  1945. #endif /* ENABLE_FEATURE_VI_UNDO */
  1946. p++;
  1947. } else if (c == 27) { // Is this an ESC?
  1948. cmd_mode = 0;
  1949. undo_queue_commit();
  1950. cmdcnt = 0;
  1951. end_cmd_q(); // stop adding to q
  1952. last_status_cksum = 0; // force status update
  1953. if ((dot > text) && (p[-1] != '\n')) {
  1954. p--;
  1955. }
  1956. #if ENABLE_FEATURE_VI_SETOPTS
  1957. if (autoindent) {
  1958. len = indent_len(bol);
  1959. col = get_column(bol + len);
  1960. if (len && col == indentcol && bol[len] == '\n') {
  1961. // remove autoindent from otherwise empty line
  1962. text_hole_delete(bol, bol + len - 1, undo);
  1963. p = bol;
  1964. }
  1965. }
  1966. #endif
  1967. } else if (c == 4) { // ctrl-D reduces indentation
  1968. char *r = bol + indent_len(bol);
  1969. int prev = prev_tabstop(get_column(r));
  1970. while (r > bol && get_column(r) > prev) {
  1971. if (p > bol)
  1972. p--;
  1973. r--;
  1974. r = text_hole_delete(r, r, ALLOW_UNDO_QUEUED);
  1975. }
  1976. #if ENABLE_FEATURE_VI_SETOPTS
  1977. if (autoindent && indentcol && r == end_line(p)) {
  1978. // record changed size of autoindent
  1979. indentcol = get_column(p);
  1980. return p;
  1981. }
  1982. #endif
  1983. #if ENABLE_FEATURE_VI_SETOPTS
  1984. } else if (c == '\t' && expandtab) { // expand tab
  1985. col = get_column(p);
  1986. col = next_tabstop(col) - col + 1;
  1987. while (col--) {
  1988. # if ENABLE_FEATURE_VI_UNDO
  1989. undo_push_insert(p, 1, undo);
  1990. # else
  1991. modified_count++;
  1992. # endif
  1993. p += 1 + stupid_insert(p, ' ');
  1994. }
  1995. #endif
  1996. } else if (isbackspace(c)) {
  1997. if (cmd_mode == 2) {
  1998. // special treatment for backspace in Replace mode
  1999. if (p > rstart) {
  2000. p--;
  2001. #if ENABLE_FEATURE_VI_UNDO
  2002. undo_pop();
  2003. #endif
  2004. }
  2005. } else if (p > text) {
  2006. p--;
  2007. p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
  2008. }
  2009. } else {
  2010. // insert a char into text[]
  2011. if (c == 13)
  2012. c = '\n'; // translate \r to \n
  2013. #if ENABLE_FEATURE_VI_UNDO
  2014. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  2015. if (c == '\n')
  2016. undo_queue_commit();
  2017. # endif
  2018. undo_push_insert(p, 1, undo);
  2019. #else
  2020. modified_count++;
  2021. #endif /* ENABLE_FEATURE_VI_UNDO */
  2022. p += 1 + stupid_insert(p, c); // insert the char
  2023. #if ENABLE_FEATURE_VI_SETOPTS
  2024. if (showmatch && strchr(")]}", c) != NULL) {
  2025. showmatching(p - 1);
  2026. }
  2027. if (autoindent && c == '\n') { // auto indent the new line
  2028. if (newindent < 0) {
  2029. // use indent of previous line
  2030. bol = prev_line(p);
  2031. len = indent_len(bol);
  2032. col = get_column(bol + len);
  2033. if (len && col == indentcol) {
  2034. // previous line was empty except for autoindent
  2035. // move the indent to the current line
  2036. memmove(bol + 1, bol, len);
  2037. *bol = '\n';
  2038. return p;
  2039. }
  2040. } else {
  2041. // for 'O'/'cc' commands add indent before newly inserted NL
  2042. if (p != end - 1) // but not for 'cc' at EOF
  2043. p--;
  2044. col = newindent;
  2045. }
  2046. if (col) {
  2047. // only record indent if in insert/replace mode or for
  2048. // the 'o'/'O'/'cc' commands, which are switched to
  2049. // insert mode early.
  2050. indentcol = cmd_mode != 0 ? col : 0;
  2051. if (expandtab) {
  2052. ntab = 0;
  2053. nspc = col;
  2054. } else {
  2055. ntab = col / tabstop;
  2056. nspc = col % tabstop;
  2057. }
  2058. p += text_hole_make(p, ntab + nspc);
  2059. #if ENABLE_FEATURE_VI_UNDO
  2060. undo_push_insert(p, ntab + nspc, undo);
  2061. #endif
  2062. rt_memset(p, '\t', ntab);
  2063. p += ntab;
  2064. rt_memset(p, ' ', nspc);
  2065. return p + nspc;
  2066. }
  2067. }
  2068. #endif
  2069. }
  2070. #if ENABLE_FEATURE_VI_SETOPTS
  2071. indentcol = 0;
  2072. #undef indentcol
  2073. #endif
  2074. return p;
  2075. }
  2076. // might reallocate text[]! use p += stupid_insert(p, ...),
  2077. // and be careful to not use pointers into potentially freed text[]!
  2078. static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
  2079. {
  2080. uintptr_t bias;
  2081. bias = text_hole_make(p, 1);
  2082. p += bias;
  2083. *p = c;
  2084. return bias;
  2085. }
  2086. static int at_eof(const char *s)
  2087. {
  2088. // does 's' point to end of file, even with no terminating newline?
  2089. return ((s == end - 2 && s[1] == '\n') || s == end - 1);
  2090. }
  2091. static int find_range(char **start, char **stop, int cmd)
  2092. {
  2093. char *p, *q, *t;
  2094. int buftype = -1;
  2095. int c;
  2096. p = q = dot;
  2097. #if ENABLE_FEATURE_VI_YANKMARK
  2098. if (cmd == 'Y') {
  2099. c = 'y';
  2100. } else
  2101. #endif
  2102. {
  2103. c = get_motion_char();
  2104. }
  2105. #if ENABLE_FEATURE_VI_YANKMARK
  2106. if ((cmd == 'Y' || cmd == c) && strchr("cdy><", c)) {
  2107. #else
  2108. if (cmd == c && strchr("cd><", c)) {
  2109. #endif
  2110. // these cmds operate on whole lines
  2111. buftype = WHOLE;
  2112. if (--cmdcnt > 0) {
  2113. do_cmd('j');
  2114. if (cmd_error)
  2115. buftype = -1;
  2116. }
  2117. } else if (strchr("^%$0bBeEfFtThnN/?|{}\b\177", c)) {
  2118. // Most operate on char positions within a line. Of those that
  2119. // don't '%' needs no special treatment, search commands are
  2120. // marked as MULTI and "{}" are handled below.
  2121. buftype = strchr("nN/?", c) ? MULTI : PARTIAL;
  2122. do_cmd(c); // execute movement cmd
  2123. if (cmd_error)
  2124. buftype = -1;
  2125. if (p == dot) // no movement is an error
  2126. buftype = -1;
  2127. } else if (strchr("wW", c)) {
  2128. buftype = MULTI;
  2129. do_cmd(c); // execute movement cmd
  2130. // step back one char, but not if we're at end of file,
  2131. // or if we are at EOF and search was for 'w' and we're at
  2132. // the start of a 'W' word.
  2133. if (dot > p && (!at_eof(dot) || (c == 'w' && ispunct(*dot))))
  2134. dot--;
  2135. t = dot;
  2136. // don't include trailing WS as part of word
  2137. while (dot > p && isspace(*dot)) {
  2138. if (*dot-- == '\n')
  2139. t = dot;
  2140. }
  2141. // for non-change operations WS after NL is not part of word
  2142. if (cmd != 'c' && dot != t && *dot != '\n')
  2143. dot = t;
  2144. } else if (strchr("GHL+-gjk'\r\n", c)) {
  2145. // these operate on whole lines
  2146. buftype = WHOLE;
  2147. do_cmd(c); // execute movement cmd
  2148. if (cmd_error)
  2149. buftype = -1;
  2150. } else if (c == ' ' || c == 'l') {
  2151. // forward motion by character
  2152. int tmpcnt = (cmdcnt ? cmdcnt : 1);
  2153. buftype = PARTIAL;
  2154. do_cmd(c); // execute movement cmd
  2155. // exclude last char unless range isn't what we expected
  2156. // this indicates we've hit EOL
  2157. if (tmpcnt == dot - p)
  2158. dot--;
  2159. }
  2160. if (buftype == -1) {
  2161. if (c != 27)
  2162. indicate_error();
  2163. return buftype;
  2164. }
  2165. q = dot;
  2166. if (q < p) {
  2167. t = q;
  2168. q = p;
  2169. p = t;
  2170. }
  2171. // movements which don't include end of range
  2172. if (q > p) {
  2173. if (strchr("^0bBFThnN/?|\b\177", c)) {
  2174. q--;
  2175. } else if (strchr("{}", c)) {
  2176. buftype = (p == begin_line(p) && (*q == '\n' || at_eof(q))) ?
  2177. WHOLE : MULTI;
  2178. if (!at_eof(q)) {
  2179. q--;
  2180. if (q > p && p != begin_line(p))
  2181. q--;
  2182. }
  2183. }
  2184. }
  2185. *start = p;
  2186. *stop = q;
  2187. return buftype;
  2188. }
  2189. static int st_test(char *p, int type, int dir, char *tested)
  2190. {
  2191. char c, c0, ci;
  2192. int test, inc;
  2193. inc = dir;
  2194. c = c0 = p[0];
  2195. ci = p[inc];
  2196. test = 0;
  2197. if (type == S_BEFORE_WS) {
  2198. c = ci;
  2199. test = (!isspace((unsigned char)c) || c == '\n');
  2200. }
  2201. if (type == S_TO_WS) {
  2202. c = c0;
  2203. test = (!isspace((unsigned char)c) || c == '\n');
  2204. }
  2205. if (type == S_OVER_WS) {
  2206. c = c0;
  2207. test = isspace((unsigned char)c);
  2208. }
  2209. if (type == S_END_PUNCT) {
  2210. c = ci;
  2211. test = ispunct((unsigned char)c);
  2212. }
  2213. if (type == S_END_ALNUM) {
  2214. c = ci;
  2215. test = (isalnum((unsigned char)c) || c == '_');
  2216. }
  2217. *tested = c;
  2218. return test;
  2219. }
  2220. static char *skip_thing(char *p, int linecnt, int dir, int type)
  2221. {
  2222. char c;
  2223. while (st_test(p, type, dir, &c)) {
  2224. // make sure we limit search to correct number of lines
  2225. if (c == '\n' && --linecnt < 1)
  2226. break;
  2227. if (dir >= 0 && p >= end - 1)
  2228. break;
  2229. if (dir < 0 && p <= text)
  2230. break;
  2231. p += dir; // move to next char
  2232. }
  2233. return p;
  2234. }
  2235. // find matching char of pair () [] {}
  2236. // will crash if c is not one of these
  2237. static char *find_pair(char *p, const char c)
  2238. {
  2239. const char *braces = "()[]{}";
  2240. char match;
  2241. int dir, level;
  2242. dir = strchr(braces, c) - braces;
  2243. dir ^= 1;
  2244. match = braces[dir];
  2245. dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
  2246. // look for match, count levels of pairs (( ))
  2247. level = 1;
  2248. for (;;) {
  2249. p += dir;
  2250. if (p < text || p >= end)
  2251. return NULL;
  2252. if (*p == c)
  2253. level++; // increase pair levels
  2254. if (*p == match) {
  2255. level--; // reduce pair level
  2256. if (level == 0)
  2257. return p; // found matching pair
  2258. }
  2259. }
  2260. }
  2261. #if ENABLE_FEATURE_VI_SETOPTS
  2262. // show the matching char of a pair, () [] {}
  2263. static void showmatching(char *p)
  2264. {
  2265. char *q, *save_dot;
  2266. // we found half of a pair
  2267. q = find_pair(p, *p); // get loc of matching char
  2268. if (q == NULL) {
  2269. indicate_error(); // no matching char
  2270. } else {
  2271. // "q" now points to matching pair
  2272. save_dot = dot; // remember where we are
  2273. dot = q; // go to new loc
  2274. refresh(FALSE); // let the user see it
  2275. mysleep(40); // give user some time
  2276. dot = save_dot; // go back to old loc
  2277. refresh(FALSE);
  2278. }
  2279. }
  2280. #endif /* FEATURE_VI_SETOPTS */
  2281. #if ENABLE_FEATURE_VI_UNDO
  2282. static void flush_undo_data(void)
  2283. {
  2284. struct undo_object *undo_entry;
  2285. while (undo_stack_tail) {
  2286. undo_entry = undo_stack_tail;
  2287. undo_stack_tail = undo_entry->prev;
  2288. vi_free(undo_entry);
  2289. }
  2290. }
  2291. // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
  2292. // Add to the undo stack
  2293. static void undo_push(char *src, unsigned length, int u_type)
  2294. {
  2295. struct undo_object *undo_entry;
  2296. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  2297. int use_spos = u_type & UNDO_USE_SPOS;
  2298. # endif
  2299. // "u_type" values
  2300. // UNDO_INS: insertion, undo will remove from buffer
  2301. // UNDO_DEL: deleted text, undo will restore to buffer
  2302. // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
  2303. // The CHAIN operations are for handling multiple operations that the user
  2304. // performs with a single action, i.e. REPLACE mode or find-and-replace commands
  2305. // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
  2306. // for the INS/DEL operation.
  2307. // UNDO_{INS,DEL} ORed with UNDO_USE_SPOS: commit the undo queue
  2308. #if ENABLE_FEATURE_VI_UNDO_QUEUE
  2309. // This undo queuing functionality groups multiple character typing or backspaces
  2310. // into a single large undo object. This greatly reduces calls to malloc() for
  2311. // single-character operations while typing and has the side benefit of letting
  2312. // an undo operation remove chunks of text rather than a single character.
  2313. switch (u_type) {
  2314. case UNDO_EMPTY: // Just in case this ever happens...
  2315. return;
  2316. case UNDO_DEL_QUEUED:
  2317. if (length != 1)
  2318. return; // Only queue single characters
  2319. switch (undo_queue_state) {
  2320. case UNDO_EMPTY:
  2321. undo_queue_state = UNDO_DEL;
  2322. case UNDO_DEL:
  2323. undo_queue_spos = src;
  2324. undo_q++;
  2325. undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
  2326. // If queue is full, dump it into an object
  2327. if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
  2328. undo_queue_commit();
  2329. return;
  2330. case UNDO_INS:
  2331. // Switch from storing inserted text to deleted text
  2332. undo_queue_commit();
  2333. undo_push(src, length, UNDO_DEL_QUEUED);
  2334. return;
  2335. }
  2336. break;
  2337. case UNDO_INS_QUEUED:
  2338. if (length < 1)
  2339. return;
  2340. switch (undo_queue_state) {
  2341. case UNDO_EMPTY:
  2342. undo_queue_state = UNDO_INS;
  2343. undo_queue_spos = src;
  2344. case UNDO_INS:
  2345. while (length--) {
  2346. undo_q++; // Don't need to save any data for insertions
  2347. if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
  2348. undo_queue_commit();
  2349. }
  2350. return;
  2351. case UNDO_DEL:
  2352. // Switch from storing deleted text to inserted text
  2353. undo_queue_commit();
  2354. undo_push(src, length, UNDO_INS_QUEUED);
  2355. return;
  2356. }
  2357. break;
  2358. }
  2359. #else
  2360. u_type &= ~UNDO_USE_SPOS;
  2361. #endif
  2362. // Allocate a new undo object
  2363. if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
  2364. // For UNDO_DEL objects, save deleted text
  2365. if ((text + length) == end)
  2366. length--;
  2367. // If this deletion empties text[], strip the newline. When the buffer becomes
  2368. // zero-length, a newline is added back, which requires this to compensate.
  2369. undo_entry = vi_zalloc(offsetof(struct undo_object, undo_text) + length);
  2370. rt_memcpy(undo_entry->undo_text, src, length);
  2371. } else {
  2372. undo_entry = vi_zalloc(sizeof(*undo_entry));
  2373. }
  2374. undo_entry->length = length;
  2375. #if ENABLE_FEATURE_VI_UNDO_QUEUE
  2376. if (use_spos) {
  2377. undo_entry->start = undo_queue_spos - text; // use start position from queue
  2378. } else {
  2379. undo_entry->start = src - text; // use offset from start of text buffer
  2380. }
  2381. #else
  2382. undo_entry->start = src - text;
  2383. #endif
  2384. undo_entry->u_type = u_type;
  2385. // Push it on undo stack
  2386. undo_entry->prev = undo_stack_tail;
  2387. undo_stack_tail = undo_entry;
  2388. modified_count++;
  2389. }
  2390. static void undo_push_insert(char *p, int len, int undo)
  2391. {
  2392. switch (undo) {
  2393. case ALLOW_UNDO:
  2394. undo_push(p, len, UNDO_INS);
  2395. break;
  2396. case ALLOW_UNDO_CHAIN:
  2397. undo_push(p, len, UNDO_INS_CHAIN);
  2398. break;
  2399. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  2400. case ALLOW_UNDO_QUEUED:
  2401. undo_push(p, len, UNDO_INS_QUEUED);
  2402. break;
  2403. # endif
  2404. }
  2405. }
  2406. // Undo the last operation
  2407. static void undo_pop(void)
  2408. {
  2409. int repeat;
  2410. char *u_start, *u_end;
  2411. struct undo_object *undo_entry;
  2412. // Commit pending undo queue before popping (should be unnecessary)
  2413. undo_queue_commit();
  2414. undo_entry = undo_stack_tail;
  2415. // Check for an empty undo stack
  2416. if (!undo_entry) {
  2417. status_line("Already at oldest change");
  2418. return;
  2419. }
  2420. switch (undo_entry->u_type) {
  2421. case UNDO_DEL:
  2422. case UNDO_DEL_CHAIN:
  2423. // make hole and put in text that was deleted; deallocate text
  2424. u_start = text + undo_entry->start;
  2425. text_hole_make(u_start, undo_entry->length);
  2426. rt_memcpy(u_start, undo_entry->undo_text, undo_entry->length);
  2427. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  2428. status_line("Undo [%d] %s %d chars at position %d",
  2429. modified_count, "restored",
  2430. undo_entry->length, undo_entry->start
  2431. );
  2432. # endif
  2433. break;
  2434. case UNDO_INS:
  2435. case UNDO_INS_CHAIN:
  2436. // delete what was inserted
  2437. u_start = undo_entry->start + text;
  2438. u_end = u_start - 1 + undo_entry->length;
  2439. text_hole_delete(u_start, u_end, NO_UNDO);
  2440. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  2441. status_line("Undo [%d] %s %d chars at position %d",
  2442. modified_count, "deleted",
  2443. undo_entry->length, undo_entry->start
  2444. );
  2445. # endif
  2446. break;
  2447. }
  2448. repeat = 0;
  2449. switch (undo_entry->u_type) {
  2450. // If this is the end of a chain, lower modification count and refresh display
  2451. case UNDO_DEL:
  2452. case UNDO_INS:
  2453. dot = (text + undo_entry->start);
  2454. refresh(FALSE);
  2455. break;
  2456. case UNDO_DEL_CHAIN:
  2457. case UNDO_INS_CHAIN:
  2458. repeat = 1;
  2459. break;
  2460. }
  2461. // Deallocate the undo object we just processed
  2462. undo_stack_tail = undo_entry->prev;
  2463. vi_free(undo_entry);
  2464. modified_count--;
  2465. // For chained operations, continue popping all the way down the chain.
  2466. if (repeat) {
  2467. undo_pop(); // Follow the undo chain if one exists
  2468. }
  2469. }
  2470. #if ENABLE_FEATURE_VI_UNDO_QUEUE
  2471. // Flush any queued objects to the undo stack
  2472. static void undo_queue_commit(void)
  2473. {
  2474. // Pushes the queue object onto the undo stack
  2475. if (undo_q > 0) {
  2476. // Deleted character undo events grow from the end
  2477. undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
  2478. undo_q,
  2479. (undo_queue_state | UNDO_USE_SPOS)
  2480. );
  2481. undo_queue_state = UNDO_EMPTY;
  2482. undo_q = 0;
  2483. }
  2484. }
  2485. #endif
  2486. #endif /* ENABLE_FEATURE_VI_UNDO */
  2487. // open a hole in text[]
  2488. // might reallocate text[]! use p += text_hole_make(p, ...),
  2489. // and be careful to not use pointers into potentially freed text[]!
  2490. static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
  2491. {
  2492. uintptr_t bias = 0;
  2493. if (size <= 0)
  2494. return bias;
  2495. end += size; // adjust the new END
  2496. if (end >= (text + text_size)) {
  2497. char *new_text;
  2498. text_size += end - (text + text_size) + 10240;
  2499. new_text = vi_realloc(text, text_size);
  2500. bias = (new_text - text);
  2501. screenbegin += bias;
  2502. dot += bias;
  2503. end += bias;
  2504. p += bias;
  2505. #if ENABLE_FEATURE_VI_YANKMARK
  2506. {
  2507. int i;
  2508. for (i = 0; i < ARRAY_SIZE(mark); i++)
  2509. if (mark[i])
  2510. mark[i] += bias;
  2511. }
  2512. #endif
  2513. text = new_text;
  2514. }
  2515. memmove(p + size, p, end - size - p);
  2516. rt_memset(p, ' ', size); // clear new hole
  2517. return bias;
  2518. }
  2519. // close a hole in text[]
  2520. // "undo" value indicates if this operation should be undo-able
  2521. static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
  2522. {
  2523. char *src, *dest;
  2524. int cnt, hole_size;
  2525. // move forwards, from beginning
  2526. // assume p <= q
  2527. src = q + 1;
  2528. dest = p;
  2529. if (q < p) { // they are backward- swap them
  2530. src = p + 1;
  2531. dest = q;
  2532. }
  2533. hole_size = q - p + 1;
  2534. cnt = end - src;
  2535. #if ENABLE_FEATURE_VI_UNDO
  2536. switch (undo) {
  2537. case NO_UNDO:
  2538. break;
  2539. case ALLOW_UNDO:
  2540. undo_push(p, hole_size, UNDO_DEL);
  2541. break;
  2542. case ALLOW_UNDO_CHAIN:
  2543. undo_push(p, hole_size, UNDO_DEL_CHAIN);
  2544. break;
  2545. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  2546. case ALLOW_UNDO_QUEUED:
  2547. undo_push(p, hole_size, UNDO_DEL_QUEUED);
  2548. break;
  2549. # endif
  2550. }
  2551. modified_count--;
  2552. #endif
  2553. if (src < text || src > end)
  2554. goto thd0;
  2555. if (dest < text || dest >= end)
  2556. goto thd0;
  2557. modified_count++;
  2558. if (src >= end)
  2559. goto thd_atend; // just delete the end of the buffer
  2560. memmove(dest, src, cnt);
  2561. thd_atend:
  2562. end = end - hole_size; // adjust the new END
  2563. if (dest >= end)
  2564. dest = end - 1; // make sure dest in below end-1
  2565. if (end <= text)
  2566. dest = end = text; // keep pointers valid
  2567. thd0:
  2568. return dest;
  2569. }
  2570. // copy text into register, then delete text.
  2571. //
  2572. static char *yank_delete(char *start, char *stop, int buftype, int yf, int undo)
  2573. {
  2574. char *p;
  2575. // make sure start <= stop
  2576. if (start > stop) {
  2577. // they are backwards, reverse them
  2578. p = start;
  2579. start = stop;
  2580. stop = p;
  2581. }
  2582. if (buftype == PARTIAL && *start == '\n')
  2583. return start;
  2584. p = start;
  2585. #if ENABLE_FEATURE_VI_YANKMARK
  2586. text_yank(start, stop, YDreg, buftype);
  2587. #endif
  2588. if (yf == YANKDEL) {
  2589. p = text_hole_delete(start, stop, undo);
  2590. } // delete lines
  2591. return p;
  2592. }
  2593. static void show_help(void)
  2594. {
  2595. vi_puts("These features are available:"
  2596. #if ENABLE_FEATURE_VI_SEARCH
  2597. "\n\tPattern searches with / and ?"
  2598. #endif
  2599. #if ENABLE_FEATURE_VI_DOT_CMD
  2600. "\n\tLast command repeat with ."
  2601. #endif
  2602. #if ENABLE_FEATURE_VI_YANKMARK
  2603. "\n\tLine marking with 'x"
  2604. "\n\tNamed buffers with \"x"
  2605. #endif
  2606. #if ENABLE_FEATURE_VI_READONLY
  2607. //not implemented: "\n\tReadonly if vi is called as \"view\""
  2608. //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
  2609. #endif
  2610. #if ENABLE_FEATURE_VI_SET
  2611. "\n\tSome colon mode commands with :"
  2612. #endif
  2613. #if ENABLE_FEATURE_VI_SETOPTS
  2614. "\n\tSettable options with \":set\""
  2615. #endif
  2616. #if ENABLE_FEATURE_VI_WIN_RESIZE
  2617. "\n\tAdapt to window re-sizes"
  2618. #endif
  2619. );
  2620. }
  2621. #if ENABLE_FEATURE_VI_DOT_CMD
  2622. static void start_new_cmd_q(char c)
  2623. {
  2624. // get buffer for new cmd
  2625. dotcnt = cmdcnt ? cmdcnt : 1;
  2626. last_modifying_cmd[0] = c;
  2627. lmc_len = 1;
  2628. adding2q = 1;
  2629. }
  2630. static void end_cmd_q(void)
  2631. {
  2632. #if ENABLE_FEATURE_VI_YANKMARK
  2633. YDreg = 26; // go back to default Yank/Delete reg
  2634. #endif
  2635. adding2q = 0;
  2636. }
  2637. #endif /* FEATURE_VI_DOT_CMD */
  2638. #if ENABLE_FEATURE_VI_YANKMARK \
  2639. || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH)
  2640. // might reallocate text[]! use p += string_insert(p, ...),
  2641. // and be careful to not use pointers into potentially freed text[]!
  2642. static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
  2643. {
  2644. uintptr_t bias;
  2645. int i;
  2646. i = strlen(s);
  2647. #if ENABLE_FEATURE_VI_UNDO
  2648. undo_push_insert(p, i, undo);
  2649. #endif
  2650. bias = text_hole_make(p, i);
  2651. p += bias;
  2652. rt_memcpy(p, s, i);
  2653. return bias;
  2654. }
  2655. #endif
  2656. //----- Block insert/delete, undo ops --------------------------
  2657. #if ENABLE_FEATURE_VI_YANKMARK
  2658. // copy text into a register
  2659. static char *text_yank(char *p, char *q, int dest, int buftype)
  2660. {
  2661. char *oldreg = reg[dest];
  2662. int cnt = q - p;
  2663. if (cnt < 0) { // they are backwards- reverse them
  2664. p = q;
  2665. cnt = -cnt;
  2666. }
  2667. // Don't free register yet. This prevents the memory allocator
  2668. // from reusing the free block so we can detect if it's changed.
  2669. reg[dest] = vi_strndup(p, cnt + 1);
  2670. vi_free(oldreg);
  2671. return p;
  2672. }
  2673. static char what_reg(void)
  2674. {
  2675. char c;
  2676. c = 'D'; // default to D-reg
  2677. if (YDreg <= 25)
  2678. c = 'a' + (char) YDreg;
  2679. if (YDreg == 26)
  2680. c = 'D';
  2681. if (YDreg == 27)
  2682. c = 'U';
  2683. return c;
  2684. }
  2685. static void check_context(char cmd)
  2686. {
  2687. // Certain movement commands update the context.
  2688. if (strchr(":%{}'GHLMz/?Nn", cmd) != NULL) {
  2689. mark[27] = mark[26]; // move cur to prev
  2690. mark[26] = dot; // move local to cur
  2691. }
  2692. }
  2693. static char *swap_context(char *p) // goto new context for '' command make this the current context
  2694. {
  2695. char *tmp;
  2696. // the current context is in mark[26]
  2697. // the previous context is in mark[27]
  2698. // only swap context if other context is valid
  2699. if (text <= mark[27] && mark[27] <= end - 1) {
  2700. tmp = mark[27];
  2701. mark[27] = mark[26];
  2702. mark[26] = tmp;
  2703. p = mark[26]; // where we are going- previous context
  2704. }
  2705. return p;
  2706. }
  2707. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  2708. static void yank_status(const char *op, const char *p, int cnt)
  2709. {
  2710. int lines, chars;
  2711. lines = chars = 0;
  2712. while (*p) {
  2713. ++chars;
  2714. if (*p++ == '\n')
  2715. ++lines;
  2716. }
  2717. status_line("%s %d lines (%d chars) from [%c]",
  2718. op, lines * cnt, chars * cnt, what_reg());
  2719. }
  2720. # endif
  2721. #endif /* FEATURE_VI_YANKMARK */
  2722. //----- Set terminal attributes --------------------------------
  2723. static void rawmode(void)
  2724. {
  2725. #ifdef RT_USING_POSIX_TERMIOS
  2726. // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
  2727. set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
  2728. #endif
  2729. }
  2730. static void cookmode(void)
  2731. {
  2732. #ifdef RT_USING_POSIX_TERMIOS
  2733. tcsetattr_stdin_TCSANOW(&term_orig);
  2734. #endif
  2735. }
  2736. // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
  2737. static int mysleep(int hund)
  2738. {
  2739. struct pollfd pfd[1];
  2740. pfd[0].fd = STDIN_FILENO;
  2741. pfd[0].events = POLLIN;
  2742. return safe_poll(pfd, 1, hund*10) > 0;
  2743. }
  2744. //----- IO Routines --------------------------------------------
  2745. static int readit(void) // read (maybe cursor) key from stdin
  2746. {
  2747. int c;
  2748. // Wait for input. TIMEOUT = -1 makes read_key wait even
  2749. // on nonblocking stdin.
  2750. // Note: read_key sets errno to 0 on success.
  2751. again:
  2752. c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
  2753. if (c == -1) { // EOF/error
  2754. if (errno == EAGAIN) // paranoia
  2755. goto again;
  2756. go_bottom_and_clear_to_eol();
  2757. cookmode(); // terminal to "cooked"
  2758. LOG_E("can't read user input");
  2759. }
  2760. return c;
  2761. }
  2762. #if ENABLE_FEATURE_VI_DOT_CMD
  2763. static int get_one_char(void)
  2764. {
  2765. int c;
  2766. if (!adding2q) {
  2767. // we are not adding to the q.
  2768. // but, we may be reading from a saved q.
  2769. // (checking "ioq" for NULL is wrong, it's not reset to NULL
  2770. // when done - "ioq_start" is reset instead).
  2771. if (ioq_start != NULL) {
  2772. // there is a queue to get chars from.
  2773. // careful with correct sign expansion!
  2774. c = (unsigned char)*ioq++;
  2775. if (c != '\0')
  2776. return c;
  2777. // the end of the q
  2778. vi_free(ioq_start);
  2779. ioq_start = NULL;
  2780. // read from STDIN:
  2781. }
  2782. return readit();
  2783. }
  2784. // we are adding STDIN chars to q.
  2785. c = readit();
  2786. if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 2) {
  2787. // last_modifying_cmd[] is too small, can't remember the cmd
  2788. // - drop it
  2789. adding2q = 0;
  2790. lmc_len = 0;
  2791. } else {
  2792. last_modifying_cmd[lmc_len++] = c;
  2793. }
  2794. return c;
  2795. }
  2796. #endif
  2797. // Get type of thing to operate on and adjust count
  2798. static int get_motion_char(void)
  2799. {
  2800. int c, cnt;
  2801. c = get_one_char();
  2802. if (isdigit(c)) {
  2803. if (c != '0') {
  2804. // get any non-zero motion count
  2805. for (cnt = 0; isdigit(c); c = get_one_char())
  2806. cnt = cnt * 10 + (c - '0');
  2807. cmdcnt = (cmdcnt ? cmdcnt : 1) * cnt;
  2808. } else {
  2809. // ensure standalone '0' works
  2810. cmdcnt = 0;
  2811. }
  2812. }
  2813. return c;
  2814. }
  2815. // Get input line (uses "status line" area)
  2816. static char *get_input_line(const char *prompt)
  2817. {
  2818. // char [MAX_INPUT_LEN]
  2819. #define buf get_input_line__buf
  2820. int c;
  2821. int i;
  2822. strcpy(buf, prompt);
  2823. last_status_cksum = 0; // force status update
  2824. go_bottom_and_clear_to_eol();
  2825. vi_puts(buf); // write out the :, /, or ? prompt
  2826. i = strlen(buf);
  2827. while (i < MAX_INPUT_LEN - 1) {
  2828. c = get_one_char();
  2829. if (c == '\n' || c == '\r' || c == 27)
  2830. break; // this is end of input
  2831. if (isbackspace(c)) {
  2832. // user wants to erase prev char
  2833. buf[--i] = '\0';
  2834. go_bottom_and_clear_to_eol();
  2835. if (i <= 0) // user backs up before b-o-l, exit
  2836. break;
  2837. vi_puts(buf);
  2838. } else if (c > 0 && c < 256) { // exclude Unicode
  2839. // (TODO: need to handle Unicode)
  2840. buf[i] = c;
  2841. buf[++i] = '\0';
  2842. vi_putchar(c);
  2843. }
  2844. }
  2845. refresh(FALSE);
  2846. return buf;
  2847. #undef buf
  2848. }
  2849. // might reallocate text[]!
  2850. static int file_insert(const char *fn, char *p, int initial)
  2851. {
  2852. int cnt = -1;
  2853. int fd, size;
  2854. struct stat statbuf;
  2855. if (p < text || p > end) {
  2856. status_line_bold("Trying to insert file outside of memory");
  2857. return cnt;
  2858. }
  2859. fd = open(fn, O_RDONLY, 0);
  2860. if (fd < 0) {
  2861. if (!initial)
  2862. status_line_bold_errno(fn);
  2863. return cnt;
  2864. }
  2865. /* Validate file */
  2866. if (fstat(fd, &statbuf) < 0) {
  2867. status_line_bold_errno(fn);
  2868. goto fi;
  2869. }
  2870. if (!S_ISREG(statbuf.st_mode)) {
  2871. status_line_bold("'%s' is not a regular file", fn);
  2872. goto fi;
  2873. }
  2874. size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
  2875. p += text_hole_make(p, size);
  2876. cnt = full_read(fd, p, size);
  2877. if (cnt < 0) {
  2878. status_line_bold_errno(fn);
  2879. p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
  2880. } else if (cnt < size) {
  2881. // There was a partial read, shrink unused space
  2882. p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
  2883. status_line_bold("can't read '%s'", fn);
  2884. }
  2885. # if ENABLE_FEATURE_VI_UNDO
  2886. else {
  2887. undo_push_insert(p, size, ALLOW_UNDO);
  2888. }
  2889. # endif
  2890. fi:
  2891. close(fd);
  2892. #if ENABLE_FEATURE_VI_READONLY
  2893. if (initial
  2894. && ((access(fn, W_OK) < 0) ||
  2895. /* root will always have access()
  2896. * so we check fileperms too */
  2897. !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
  2898. )
  2899. ) {
  2900. SET_READONLY_FILE(readonly_mode);
  2901. }
  2902. #endif
  2903. return cnt;
  2904. }
  2905. static int file_write(char *fn, char *first, char *last)
  2906. {
  2907. int fd, cnt, charcnt;
  2908. if (fn == 0) {
  2909. status_line_bold("No current filename");
  2910. return -2;
  2911. }
  2912. /* By popular request we do not open file with O_TRUNC,
  2913. * but instead ftruncate() it _after_ successful write.
  2914. * Might reduce amount of data lost on power fail etc.
  2915. */
  2916. // ftruncate nosys + O_TRUNC
  2917. fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0666);
  2918. if (fd < 0)
  2919. return -1;
  2920. cnt = last - first + 1;
  2921. charcnt = full_write(fd, first, cnt);
  2922. // ftruncate(fd, charcnt);
  2923. if (charcnt == cnt) {
  2924. // good write
  2925. //modified_count = FALSE;
  2926. } else {
  2927. charcnt = 0;
  2928. }
  2929. close(fd);
  2930. return charcnt;
  2931. }
  2932. //----- Terminal Drawing ---------------------------------------
  2933. // The terminal is made up of 'rows' line of 'columns' columns.
  2934. // classically this would be 24 x 80.
  2935. // screen coordinates
  2936. // 0,0 ... 0,79
  2937. // 1,0 ... 1,79
  2938. // . ... .
  2939. // . ... .
  2940. // 22,0 ... 22,79
  2941. // 23,0 ... 23,79 <- status line
  2942. //----- Move the cursor to row x col (count from 0, not 1) -------
  2943. static void place_cursor(int row, int col)
  2944. {
  2945. char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
  2946. if (row < 0) row = 0;
  2947. if (row >= rows) row = rows - 1;
  2948. if (col < 0) col = 0;
  2949. if (col >= columns) col = columns - 1;
  2950. rt_sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
  2951. vi_puts(cm1);
  2952. }
  2953. //----- Erase from cursor to end of line -----------------------
  2954. static void clear_to_eol(void)
  2955. {
  2956. vi_puts(ESC_CLEAR2EOL);
  2957. }
  2958. static void go_bottom_and_clear_to_eol(void)
  2959. {
  2960. place_cursor(rows - 1, 0);
  2961. clear_to_eol();
  2962. }
  2963. //----- Start standout mode ------------------------------------
  2964. static void standout_start(void)
  2965. {
  2966. vi_puts(ESC_BOLD_TEXT);
  2967. }
  2968. //----- End standout mode --------------------------------------
  2969. static void standout_end(void)
  2970. {
  2971. vi_puts(ESC_NORM_TEXT);
  2972. }
  2973. //----- Flash the screen --------------------------------------
  2974. static void flash(int h)
  2975. {
  2976. standout_start();
  2977. redraw(TRUE);
  2978. mysleep(h);
  2979. standout_end();
  2980. redraw(TRUE);
  2981. }
  2982. static void indicate_error(void)
  2983. {
  2984. cmd_error = TRUE;
  2985. if (!err_method) {
  2986. vi_puts(ESC_BELL);
  2987. } else {
  2988. flash(10);
  2989. }
  2990. }
  2991. //----- Screen[] Routines --------------------------------------
  2992. //----- Erase the Screen[] memory ------------------------------
  2993. static void screen_erase(void)
  2994. {
  2995. rt_memset(screen, ' ', screensize); // clear new screen
  2996. }
  2997. static int bufsum(char *buf, int count)
  2998. {
  2999. int sum = 0;
  3000. char *e = buf + count;
  3001. while (buf < e)
  3002. sum += (unsigned char) *buf++;
  3003. return sum;
  3004. }
  3005. //----- Draw the status line at bottom of the screen -------------
  3006. static void show_status_line(void)
  3007. {
  3008. int cnt = 0, cksum = 0;
  3009. // either we already have an error or status message, or we
  3010. // create one.
  3011. if (!have_status_msg) {
  3012. cnt = format_edit_status();
  3013. cksum = bufsum(status_buffer, cnt);
  3014. }
  3015. if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
  3016. last_status_cksum = cksum; // remember if we have seen this line
  3017. go_bottom_and_clear_to_eol();
  3018. vi_puts(status_buffer);
  3019. if (have_status_msg) {
  3020. if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
  3021. (columns - 1) ) {
  3022. have_status_msg = 0;
  3023. Hit_Return();
  3024. }
  3025. have_status_msg = 0;
  3026. }
  3027. place_cursor(crow, ccol); // put cursor back in correct place
  3028. }
  3029. }
  3030. //----- format the status buffer, the bottom line of screen ------
  3031. // format status buffer, with STANDOUT mode
  3032. static void status_line_bold(const char *format, ...)
  3033. {
  3034. va_list args;
  3035. va_start(args, format);
  3036. strcpy(status_buffer, ESC_BOLD_TEXT);
  3037. rt_vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
  3038. STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
  3039. format, args
  3040. );
  3041. strcat(status_buffer, ESC_NORM_TEXT);
  3042. va_end(args);
  3043. have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
  3044. }
  3045. static void status_line_bold_errno(const char *fn)
  3046. {
  3047. status_line_bold("'%s' %s", fn, strerror(errno));
  3048. }
  3049. //----- format the status buffer, the bottom line of screen ------
  3050. static void status_line(const char *format, ...)
  3051. {
  3052. va_list args;
  3053. va_start(args, format);
  3054. rt_vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
  3055. va_end(args);
  3056. have_status_msg = 1;
  3057. }
  3058. // copy s to buf, convert unprintable
  3059. static void print_literal(char *buf, const char *s)
  3060. {
  3061. char *d;
  3062. unsigned char c;
  3063. if (!s[0])
  3064. s = "(NULL)";
  3065. d = buf;
  3066. for (; *s; s++) {
  3067. c = *s;
  3068. if ((c & 0x80) && !Isprint(c))
  3069. c = '?';
  3070. if (c < ' ' || c == 0x7f) {
  3071. *d++ = '^';
  3072. c |= '@'; /* 0x40 */
  3073. if (c == 0x7f)
  3074. c = '?';
  3075. }
  3076. *d++ = c;
  3077. *d = '\0';
  3078. if (d - buf > MAX_INPUT_LEN - 10) // paranoia
  3079. break;
  3080. }
  3081. }
  3082. static void not_implemented(const char *s)
  3083. {
  3084. char buf[MAX_INPUT_LEN];
  3085. print_literal(buf, s);
  3086. status_line_bold("'%s' is not implemented", buf);
  3087. }
  3088. // show file status on status line
  3089. static int format_edit_status(void)
  3090. {
  3091. static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
  3092. #define tot format_edit_status__tot
  3093. int cur, percent, ret, trunc_at;
  3094. // modified_count is now a counter rather than a flag. this
  3095. // helps reduce the amount of line counting we need to do.
  3096. // (this will cause a mis-reporting of modified status
  3097. // once every MAXINT editing operations.)
  3098. // it would be nice to do a similar optimization here -- if
  3099. // we haven't done a motion that could have changed which line
  3100. // we're on, then we shouldn't have to do this count_lines()
  3101. cur = count_lines(text, dot);
  3102. // count_lines() is expensive.
  3103. // Call it only if something was changed since last time
  3104. // we were here:
  3105. if (modified_count != last_modified_count) {
  3106. tot = cur + count_lines(dot, end - 1) - 1;
  3107. last_modified_count = modified_count;
  3108. }
  3109. // current line percent
  3110. // ------------- ~~ ----------
  3111. // total lines 100
  3112. if (tot > 0) {
  3113. percent = (100 * cur) / tot;
  3114. } else {
  3115. cur = tot = 0;
  3116. percent = 100;
  3117. }
  3118. trunc_at = columns < STATUS_BUFFER_LEN-1 ?
  3119. columns : STATUS_BUFFER_LEN-1;
  3120. ret = rt_snprintf(status_buffer, trunc_at+1,
  3121. #if ENABLE_FEATURE_VI_READONLY
  3122. "%c %s%s%s %d/%d %d%%",
  3123. #else
  3124. "%c %s%s %d/%d %d%%",
  3125. #endif
  3126. cmd_mode_indicator[cmd_mode & 3],
  3127. (current_filename != NULL ? current_filename : "No file"),
  3128. #if ENABLE_FEATURE_VI_READONLY
  3129. (readonly_mode ? " [Readonly]" : ""),
  3130. #endif
  3131. (modified_count ? " [Modified]" : ""),
  3132. cur, tot, percent);
  3133. if (ret >= 0 && ret < trunc_at)
  3134. return ret; /* it all fit */
  3135. return trunc_at; /* had to truncate */
  3136. #undef tot
  3137. }
  3138. //----- Force refresh of all Lines -----------------------------
  3139. static void redraw(int full_screen)
  3140. {
  3141. // cursor to top,left; clear to the end of screen
  3142. vi_puts(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
  3143. screen_erase(); // erase the internal screen buffer
  3144. last_status_cksum = 0; // force status update
  3145. refresh(full_screen); // this will redraw the entire display
  3146. show_status_line();
  3147. }
  3148. //----- Format a text[] line into a buffer ---------------------
  3149. static char *format_line(char *src /*, int li*/)
  3150. {
  3151. unsigned char c;
  3152. int co;
  3153. int ofs = offset;
  3154. char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
  3155. c = '~'; // char in col 0 in non-existent lines is '~'
  3156. co = 0;
  3157. while (co < columns + tabstop) {
  3158. // have we gone past the end?
  3159. if (src < end) {
  3160. c = *src++;
  3161. if (c == '\n')
  3162. break;
  3163. if ((c & 0x80) && !Isprint(c)) {
  3164. c = '.';
  3165. }
  3166. if (c < ' ' || c == 0x7f) {
  3167. if (c == '\t') {
  3168. c = ' ';
  3169. // co % 8 != 7
  3170. while ((co % tabstop) != (tabstop - 1)) {
  3171. dest[co++] = c;
  3172. }
  3173. } else {
  3174. dest[co++] = '^';
  3175. if (c == 0x7f)
  3176. c = '?';
  3177. else
  3178. c += '@'; // Ctrl-X -> 'X'
  3179. }
  3180. }
  3181. }
  3182. dest[co++] = c;
  3183. // discard scrolled-off-to-the-left portion,
  3184. // in tabstop-sized pieces
  3185. if (ofs >= tabstop && co >= tabstop) {
  3186. memmove(dest, dest + tabstop, co);
  3187. co -= tabstop;
  3188. ofs -= tabstop;
  3189. }
  3190. if (src >= end)
  3191. break;
  3192. }
  3193. // check "short line, gigantic offset" case
  3194. if (co < ofs)
  3195. ofs = co;
  3196. // discard last scrolled off part
  3197. co -= ofs;
  3198. dest += ofs;
  3199. // fill the rest with spaces
  3200. if (co < columns)
  3201. rt_memset(&dest[co], ' ', columns - co);
  3202. return dest;
  3203. }
  3204. //----- Refresh the changed screen lines -----------------------
  3205. // Copy the source line from text[] into the buffer and note
  3206. // if the current screenline is different from the new buffer.
  3207. // If they differ then that line needs redrawing on the terminal.
  3208. //
  3209. static void refresh(int full_screen)
  3210. {
  3211. #define old_offset refresh__old_offset
  3212. int li, changed;
  3213. char *tp, *sp; // pointer into text[] and screen[]
  3214. if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
  3215. unsigned c = columns, r = rows;
  3216. query_screen_dimensions();
  3217. if (c != columns || r != rows) {
  3218. full_screen = TRUE;
  3219. /* update screen memory since SIGWINCH won't have done it */
  3220. new_screen(rows, columns);
  3221. }
  3222. }
  3223. sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
  3224. tp = screenbegin; // index into text[] of top line
  3225. // compare text[] to screen[] and mark screen[] lines that need updating
  3226. for (li = 0; li < rows - 1; li++) {
  3227. int cs, ce; // column start & end
  3228. char *out_buf;
  3229. // format current text line
  3230. out_buf = format_line(tp /*, li*/);
  3231. // skip to the end of the current text[] line
  3232. if (tp < end) {
  3233. char *t = memchr(tp, '\n', end - tp);
  3234. if (!t) t = end - 1;
  3235. tp = t + 1;
  3236. }
  3237. // see if there are any changes between vitual screen and out_buf
  3238. changed = FALSE; // assume no change
  3239. cs = 0;
  3240. ce = columns - 1;
  3241. sp = &screen[li * columns]; // start of screen line
  3242. if (full_screen) {
  3243. // force re-draw of every single column from 0 - columns-1
  3244. goto re0;
  3245. }
  3246. // compare newly formatted buffer with virtual screen
  3247. // look forward for first difference between buf and screen
  3248. for (; cs <= ce; cs++) {
  3249. if (out_buf[cs] != sp[cs]) {
  3250. changed = TRUE; // mark for redraw
  3251. break;
  3252. }
  3253. }
  3254. // look backward for last difference between out_buf and screen
  3255. for (; ce >= cs; ce--) {
  3256. if (out_buf[ce] != sp[ce]) {
  3257. changed = TRUE; // mark for redraw
  3258. break;
  3259. }
  3260. }
  3261. // now, cs is index of first diff, and ce is index of last diff
  3262. // if horz offset has changed, force a redraw
  3263. if (offset != old_offset) {
  3264. re0:
  3265. changed = TRUE;
  3266. }
  3267. // make a sanity check of columns indexes
  3268. if (cs < 0) cs = 0;
  3269. if (ce > columns - 1) ce = columns - 1;
  3270. if (cs > ce) { cs = 0; ce = columns - 1; }
  3271. // is there a change between vitual screen and out_buf
  3272. if (changed) {
  3273. // copy changed part of buffer to virtual screen
  3274. rt_memcpy(sp+cs, out_buf+cs, ce-cs+1);
  3275. place_cursor(li, cs);
  3276. // write line out to terminal
  3277. vi_write(&sp[cs], ce - cs + 1); /* fwrite(&sp[cs], ce - cs + 1, 1, stdout); */
  3278. }
  3279. }
  3280. place_cursor(crow, ccol);
  3281. if (!keep_index)
  3282. cindex = ccol + offset;
  3283. old_offset = offset;
  3284. #undef old_offset
  3285. }
  3286. //---------------------------------------------------------------------
  3287. //----- the Ascii Chart -----------------------------------------------
  3288. // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
  3289. // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
  3290. // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
  3291. // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
  3292. // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
  3293. // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
  3294. // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
  3295. // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
  3296. // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
  3297. // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
  3298. // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
  3299. // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
  3300. // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
  3301. // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
  3302. // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
  3303. // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
  3304. //---------------------------------------------------------------------
  3305. //----- Execute a Vi Command -----------------------------------
  3306. static void do_cmd(int c)
  3307. {
  3308. char *p, *q, *save_dot;
  3309. char buf[12];
  3310. int dir;
  3311. int cnt, i, j;
  3312. int c1;
  3313. #if ENABLE_FEATURE_VI_YANKMARK
  3314. char *orig_dot = dot;
  3315. #endif
  3316. #if ENABLE_FEATURE_VI_UNDO
  3317. int allow_undo = ALLOW_UNDO;
  3318. int undo_del = UNDO_DEL;
  3319. #endif
  3320. // c1 = c; // quiet the compiler
  3321. // cnt = yf = 0; // quiet the compiler
  3322. // p = q = save_dot = buf; // quiet the compiler
  3323. rt_memset(buf, '\0', sizeof(buf));
  3324. keep_index = FALSE;
  3325. cmd_error = FALSE;
  3326. show_status_line();
  3327. // if this is a cursor key, skip these checks
  3328. switch (c) {
  3329. case KEYCODE_UP:
  3330. case KEYCODE_DOWN:
  3331. case KEYCODE_LEFT:
  3332. case KEYCODE_RIGHT:
  3333. case KEYCODE_HOME:
  3334. case KEYCODE_END:
  3335. case KEYCODE_PAGEUP:
  3336. case KEYCODE_PAGEDOWN:
  3337. case KEYCODE_DELETE:
  3338. goto key_cmd_mode;
  3339. }
  3340. if (cmd_mode == 2) {
  3341. // flip-flop Insert/Replace mode
  3342. if (c == KEYCODE_INSERT)
  3343. goto dc_i;
  3344. // we are 'R'eplacing the current *dot with new char
  3345. if (*dot == '\n') {
  3346. // don't Replace past E-o-l
  3347. cmd_mode = 1; // convert to insert
  3348. undo_queue_commit();
  3349. } else {
  3350. if (1 <= c || Isprint(c)) {
  3351. if (c != 27 && !isbackspace(c))
  3352. dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);
  3353. dot = char_insert(dot, c, ALLOW_UNDO_CHAIN);
  3354. }
  3355. goto dc1;
  3356. }
  3357. }
  3358. if (cmd_mode == 1) {
  3359. // hitting "Insert" twice means "R" replace mode
  3360. if (c == KEYCODE_INSERT) goto dc5;
  3361. // insert the char c at "dot"
  3362. if (1 <= c || Isprint(c)) {
  3363. dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
  3364. }
  3365. goto dc1;
  3366. }
  3367. key_cmd_mode:
  3368. switch (c) {
  3369. //case 0x01: // soh
  3370. //case 0x09: // ht
  3371. //case 0x0b: // vt
  3372. //case 0x0e: // so
  3373. //case 0x0f: // si
  3374. //case 0x10: // dle
  3375. //case 0x11: // dc1
  3376. //case 0x13: // dc3
  3377. //case 0x16: // syn
  3378. //case 0x17: // etb
  3379. //case 0x18: // can
  3380. //case 0x1c: // fs
  3381. //case 0x1d: // gs
  3382. //case 0x1e: // rs
  3383. //case 0x1f: // us
  3384. //case '!': // !-
  3385. //case '#': // #-
  3386. //case '&': // &-
  3387. //case '(': // (-
  3388. //case ')': // )-
  3389. //case '*': // *-
  3390. //case '=': // =-
  3391. //case '@': // @-
  3392. //case 'K': // K-
  3393. //case 'Q': // Q-
  3394. //case 'S': // S-
  3395. //case 'V': // V-
  3396. //case '[': // [-
  3397. //case '\\': // \-
  3398. //case ']': // ]-
  3399. //case '_': // _-
  3400. //case '`': // `-
  3401. //case 'v': // v-
  3402. default: // unrecognized command
  3403. buf[0] = c;
  3404. buf[1] = '\0';
  3405. not_implemented(buf);
  3406. end_cmd_q(); // stop adding to q
  3407. case 0x00: // nul- ignore
  3408. break;
  3409. case 2: // ctrl-B scroll up full screen
  3410. case KEYCODE_PAGEUP: // Cursor Key Page Up
  3411. dot_scroll(rows - 2, -1);
  3412. break;
  3413. case 4: // ctrl-D scroll down half screen
  3414. dot_scroll((rows - 2) / 2, 1);
  3415. break;
  3416. case 5: // ctrl-E scroll down one line
  3417. dot_scroll(1, 1);
  3418. break;
  3419. case 6: // ctrl-F scroll down full screen
  3420. case KEYCODE_PAGEDOWN: // Cursor Key Page Down
  3421. dot_scroll(rows - 2, 1);
  3422. break;
  3423. case 7: // ctrl-G show current status
  3424. last_status_cksum = 0; // force status update
  3425. break;
  3426. case 'h': // h- move left
  3427. case KEYCODE_LEFT: // cursor key Left
  3428. case 8: // ctrl-H- move left (This may be ERASE char)
  3429. case 0x7f: // DEL- move left (This may be ERASE char)
  3430. do {
  3431. dot_left();
  3432. } while (--cmdcnt > 0);
  3433. break;
  3434. case 10: // Newline ^J
  3435. case 'j': // j- goto next line, same col
  3436. case KEYCODE_DOWN: // cursor key Down
  3437. case 13: // Carriage Return ^M
  3438. case '+': // +- goto next line
  3439. q = dot;
  3440. do {
  3441. p = next_line(q);
  3442. if (p == end_line(q)) {
  3443. indicate_error();
  3444. goto dc1;
  3445. }
  3446. q = p;
  3447. } while (--cmdcnt > 0);
  3448. dot = q;
  3449. if (c == 13 || c == '+') {
  3450. dot_skip_over_ws();
  3451. } else {
  3452. // try to stay in saved column
  3453. dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
  3454. keep_index = TRUE;
  3455. }
  3456. break;
  3457. case 12: // ctrl-L force redraw whole screen
  3458. case 18: // ctrl-R force redraw
  3459. redraw(TRUE); // this will redraw the entire display
  3460. break;
  3461. case 21: // ctrl-U scroll up half screen
  3462. dot_scroll((rows - 2) / 2, -1);
  3463. break;
  3464. case 25: // ctrl-Y scroll up one line
  3465. dot_scroll(1, -1);
  3466. break;
  3467. case 27: // esc
  3468. if (cmd_mode == 0)
  3469. indicate_error();
  3470. cmd_mode = 0; // stop inserting
  3471. undo_queue_commit();
  3472. end_cmd_q();
  3473. last_status_cksum = 0; // force status update
  3474. break;
  3475. case ' ': // move right
  3476. case 'l': // move right
  3477. case KEYCODE_RIGHT: // Cursor Key Right
  3478. do {
  3479. dot_right();
  3480. } while (--cmdcnt > 0);
  3481. break;
  3482. #if ENABLE_FEATURE_VI_YANKMARK
  3483. case '"': // "- name a register to use for Delete/Yank
  3484. c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
  3485. if ((unsigned)c1 <= 25) { // a-z?
  3486. YDreg = c1;
  3487. } else {
  3488. indicate_error();
  3489. }
  3490. break;
  3491. case '\'': // '- goto a specific mark
  3492. c1 = (get_one_char() | 0x20);
  3493. if ((unsigned)(c1 - 'a') <= 25) { // a-z?
  3494. c1 = (c1 - 'a');
  3495. // get the b-o-l
  3496. q = mark[c1];
  3497. if (text <= q && q < end) {
  3498. dot = q;
  3499. dot_begin(); // go to B-o-l
  3500. dot_skip_over_ws();
  3501. } else {
  3502. indicate_error();
  3503. }
  3504. } else if (c1 == '\'') { // goto previous context
  3505. dot = swap_context(dot); // swap current and previous context
  3506. dot_begin(); // go to B-o-l
  3507. dot_skip_over_ws();
  3508. #if ENABLE_FEATURE_VI_YANKMARK
  3509. orig_dot = dot; // this doesn't update stored contexts
  3510. #endif
  3511. } else {
  3512. indicate_error();
  3513. }
  3514. break;
  3515. case 'm': // m- Mark a line
  3516. // this is really stupid. If there are any inserts or deletes
  3517. // between text[0] and dot then this mark will not point to the
  3518. // correct location! It could be off by many lines!
  3519. // Well..., at least its quick and dirty.
  3520. c1 = (get_one_char() | 0x20) - 'a';
  3521. if ((unsigned)c1 <= 25) { // a-z?
  3522. // remember the line
  3523. mark[c1] = dot;
  3524. } else {
  3525. indicate_error();
  3526. }
  3527. break;
  3528. case 'P': // P- Put register before
  3529. case 'p': // p- put register after
  3530. p = reg[YDreg];
  3531. if (p == NULL) {
  3532. status_line_bold("Nothing in register %c", what_reg());
  3533. break;
  3534. }
  3535. cnt = 0;
  3536. i = cmdcnt ? cmdcnt : 1;
  3537. // are we putting whole lines or strings
  3538. if (regtype[YDreg] == WHOLE) {
  3539. if (c == 'P') {
  3540. dot_begin(); // putting lines- Put above
  3541. }
  3542. else /* if ( c == 'p') */ {
  3543. // are we putting after very last line?
  3544. if (end_line(dot) == (end - 1)) {
  3545. dot = end; // force dot to end of text[]
  3546. } else {
  3547. dot_next(); // next line, then put before
  3548. }
  3549. }
  3550. } else {
  3551. if (c == 'p')
  3552. dot_right(); // move to right, can move to NL
  3553. // how far to move cursor if register doesn't have a NL
  3554. if (strchr(p, '\n') == NULL)
  3555. cnt = i * strlen(p) - 1;
  3556. }
  3557. do {
  3558. // dot is adjusted if text[] is reallocated so we don't have to
  3559. string_insert(dot, p, allow_undo); // insert the string
  3560. # if ENABLE_FEATURE_VI_UNDO
  3561. allow_undo = ALLOW_UNDO_CHAIN;
  3562. # endif
  3563. } while (--cmdcnt > 0);
  3564. dot += cnt;
  3565. dot_skip_over_ws();
  3566. # if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
  3567. yank_status("Put", p, i);
  3568. # endif
  3569. end_cmd_q(); // stop adding to q
  3570. break;
  3571. case 'U': // U- Undo; replace current line with original version
  3572. if (reg[Ureg] != NULL) {
  3573. p = begin_line(dot);
  3574. q = end_line(dot);
  3575. p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
  3576. p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
  3577. dot = p;
  3578. dot_skip_over_ws();
  3579. # if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
  3580. yank_status("Undo", reg[Ureg], 1);
  3581. # endif
  3582. }
  3583. break;
  3584. #endif /* FEATURE_VI_YANKMARK */
  3585. #if ENABLE_FEATURE_VI_UNDO
  3586. case 'u': // u- undo last operation
  3587. undo_pop();
  3588. break;
  3589. #endif
  3590. case '$': // $- goto end of line
  3591. case KEYCODE_END: // Cursor Key End
  3592. for (;;) {
  3593. dot = end_line(dot);
  3594. if (--cmdcnt <= 0)
  3595. break;
  3596. dot_next();
  3597. }
  3598. cindex = C_END;
  3599. keep_index = TRUE;
  3600. break;
  3601. case '%': // %- find matching char of pair () [] {}
  3602. for (q = dot; q < end && *q != '\n'; q++) {
  3603. if (strchr("()[]{}", *q) != NULL) {
  3604. // we found half of a pair
  3605. p = find_pair(q, *q);
  3606. if (p == NULL) {
  3607. indicate_error();
  3608. } else {
  3609. dot = p;
  3610. }
  3611. break;
  3612. }
  3613. }
  3614. if (*q == '\n')
  3615. indicate_error();
  3616. break;
  3617. case 'f': // f- forward to a user specified char
  3618. case 'F': // F- backward to a user specified char
  3619. case 't': // t- move to char prior to next x
  3620. case 'T': // T- move to char after previous x
  3621. last_search_char = get_one_char(); // get the search char
  3622. last_search_cmd = c;
  3623. // fall through
  3624. case ';': // ;- look at rest of line for last search char
  3625. case ',': // ,- repeat latest search in opposite direction
  3626. dot_to_char(c != ',' ? last_search_cmd : last_search_cmd ^ 0x20);
  3627. break;
  3628. #if ENABLE_FEATURE_VI_DOT_CMD
  3629. case '.': // .- repeat the last modifying command
  3630. // Stuff the last_modifying_cmd back into stdin
  3631. // and let it be re-executed.
  3632. if (lmc_len != 0) {
  3633. if (cmdcnt) // update saved count if current count is non-zero
  3634. dotcnt = cmdcnt;
  3635. last_modifying_cmd[lmc_len] = '\0';
  3636. ioq = ioq_start = xasprintf("%u%s", dotcnt, last_modifying_cmd);
  3637. }
  3638. break;
  3639. #endif
  3640. #if ENABLE_FEATURE_VI_SEARCH
  3641. case 'N': // N- backward search for last pattern
  3642. dir = last_search_pattern[0] == '/' ? BACK : FORWARD;
  3643. goto dc4; // now search for pattern
  3644. case '?': // ?- backward search for a pattern
  3645. case '/': // /- forward search for a pattern
  3646. buf[0] = c;
  3647. buf[1] = '\0';
  3648. q = get_input_line(buf); // get input line- use "status line"
  3649. if (!q[0]) // user changed mind and erased the "/"- do nothing
  3650. break;
  3651. if (!q[1]) { // if no pat re-use old pat
  3652. if (last_search_pattern[0])
  3653. last_search_pattern[0] = c;
  3654. } else { // strlen(q) > 1: new pat- save it and find
  3655. vi_free(last_search_pattern);
  3656. last_search_pattern = vi_strdup(q);
  3657. }
  3658. // fall through
  3659. case 'n': // n- repeat search for last pattern
  3660. // search rest of text[] starting at next char
  3661. // if search fails "dot" is unchanged
  3662. dir = last_search_pattern[0] == '/' ? FORWARD : BACK;
  3663. dc4:
  3664. if (last_search_pattern[1] == '\0') {
  3665. status_line_bold("No previous search");
  3666. break;
  3667. }
  3668. do {
  3669. q = char_search(dot + dir, last_search_pattern + 1,
  3670. (dir << 1) | FULL);
  3671. if (q != NULL) {
  3672. dot = q; // good search, update "dot"
  3673. } else {
  3674. // no pattern found between "dot" and top/bottom of file
  3675. // continue from other end of file
  3676. const char *msg;
  3677. q = char_search(dir == FORWARD ? text : end - 1,
  3678. last_search_pattern + 1, (dir << 1) | FULL);
  3679. if (q != NULL) { // found something
  3680. dot = q; // found new pattern- goto it
  3681. msg = "search hit %s, continuing at %s";
  3682. } else { // pattern is nowhere in file
  3683. cmdcnt = 0; // force exit from loop
  3684. msg = "Pattern not found";
  3685. }
  3686. if (dir == FORWARD)
  3687. status_line_bold(msg, "BOTTOM", "TOP");
  3688. else
  3689. status_line_bold(msg, "TOP", "BOTTOM");
  3690. }
  3691. } while (--cmdcnt > 0);
  3692. break;
  3693. case '{': // {- move backward paragraph
  3694. case '}': // }- move forward paragraph
  3695. dir = c == '}' ? FORWARD : BACK;
  3696. do {
  3697. int skip = TRUE; // initially skip consecutive empty lines
  3698. while (dir == FORWARD ? dot < end - 1 : dot > text) {
  3699. if (*dot == '\n' && dot[dir] == '\n') {
  3700. if (!skip) {
  3701. if (dir == FORWARD)
  3702. ++dot; // move to next blank line
  3703. goto dc2;
  3704. }
  3705. }
  3706. else {
  3707. skip = FALSE;
  3708. }
  3709. dot += dir;
  3710. }
  3711. goto dc6; // end of file
  3712. dc2:
  3713. continue;
  3714. } while (--cmdcnt > 0);
  3715. break;
  3716. #endif /* FEATURE_VI_SEARCH */
  3717. case '0': // 0- goto beginning of line
  3718. case '1': // 1-
  3719. case '2': // 2-
  3720. case '3': // 3-
  3721. case '4': // 4-
  3722. case '5': // 5-
  3723. case '6': // 6-
  3724. case '7': // 7-
  3725. case '8': // 8-
  3726. case '9': // 9-
  3727. if (c == '0' && cmdcnt < 1) {
  3728. dot_begin(); // this was a standalone zero
  3729. } else {
  3730. cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
  3731. }
  3732. break;
  3733. case ':': // :- the colon mode commands
  3734. p = get_input_line(":"); // get input line- use "status line"
  3735. colon(p); // execute the command
  3736. break;
  3737. case '<': // <- Left shift something
  3738. case '>': // >- Right shift something
  3739. cnt = count_lines(text, dot); // remember what line we are on
  3740. if (find_range(&p, &q, c) == -1)
  3741. goto dc6;
  3742. i = count_lines(p, q); // # of lines we are shifting
  3743. for (p = begin_line(p); i > 0; i--, p = next_line(p)) {
  3744. if (c == '<') {
  3745. // shift left- remove tab or tabstop spaces
  3746. if (*p == '\t') {
  3747. // shrink buffer 1 char
  3748. text_hole_delete(p, p, allow_undo);
  3749. } else if (*p == ' ') {
  3750. // we should be calculating columns, not just SPACE
  3751. for (j = 0; *p == ' ' && j < tabstop; j++) {
  3752. text_hole_delete(p, p, allow_undo);
  3753. #if ENABLE_FEATURE_VI_UNDO
  3754. allow_undo = ALLOW_UNDO_CHAIN;
  3755. #endif
  3756. }
  3757. }
  3758. } else if (/* c == '>' && */ p != end_line(p)) {
  3759. // shift right -- add tab or tabstop spaces on non-empty lines
  3760. char_insert(p, '\t', allow_undo);
  3761. }
  3762. #if ENABLE_FEATURE_VI_UNDO
  3763. allow_undo = ALLOW_UNDO_CHAIN;
  3764. #endif
  3765. }
  3766. dot = find_line(cnt); // what line were we on
  3767. dot_skip_over_ws();
  3768. end_cmd_q(); // stop adding to q
  3769. break;
  3770. case 'A': // A- append at e-o-l
  3771. dot_end(); // go to e-o-l
  3772. //**** fall through to ... 'a'
  3773. case 'a': // a- append after current char
  3774. if (*dot != '\n')
  3775. dot++;
  3776. goto dc_i;
  3777. case 'B': // B- back a blank-delimited Word
  3778. case 'E': // E- end of a blank-delimited word
  3779. case 'W': // W- forward a blank-delimited word
  3780. dir = FORWARD;
  3781. if (c == 'B')
  3782. dir = BACK;
  3783. do {
  3784. if (c == 'W' || isspace(dot[dir])) {
  3785. dot = skip_thing(dot, 1, dir, S_TO_WS);
  3786. dot = skip_thing(dot, 2, dir, S_OVER_WS);
  3787. }
  3788. if (c != 'W')
  3789. dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
  3790. } while (--cmdcnt > 0);
  3791. break;
  3792. case 'C': // C- Change to e-o-l
  3793. case 'D': // D- delete to e-o-l
  3794. save_dot = dot;
  3795. dot = dollar_line(dot); // move to before NL
  3796. // copy text into a register and delete
  3797. dot = yank_delete(save_dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); // delete to e-o-l
  3798. if (c == 'C')
  3799. goto dc_i; // start inserting
  3800. #if ENABLE_FEATURE_VI_DOT_CMD
  3801. if (c == 'D')
  3802. end_cmd_q(); // stop adding to q
  3803. #endif
  3804. break;
  3805. case 'g': // 'gg' goto a line number (vim) (default: very first line)
  3806. c1 = get_one_char();
  3807. if (c1 != 'g') {
  3808. buf[0] = 'g';
  3809. // c1 < 0 if the key was special. Try "g<up-arrow>"
  3810. // TODO: if Unicode?
  3811. buf[1] = (c1 >= 0 ? c1 : '*');
  3812. buf[2] = '\0';
  3813. not_implemented(buf);
  3814. cmd_error = TRUE;
  3815. break;
  3816. }
  3817. if (cmdcnt == 0)
  3818. cmdcnt = 1;
  3819. // fall through
  3820. case 'G': // G- goto to a line number (default= E-O-F)
  3821. dot = end - 1; // assume E-O-F
  3822. if (cmdcnt > 0) {
  3823. dot = find_line(cmdcnt); // what line is #cmdcnt
  3824. }
  3825. dot_begin();
  3826. dot_skip_over_ws();
  3827. break;
  3828. case 'H': // H- goto top line on screen
  3829. dot = screenbegin;
  3830. if (cmdcnt > (rows - 1)) {
  3831. cmdcnt = (rows - 1);
  3832. }
  3833. while (--cmdcnt > 0) {
  3834. dot_next();
  3835. }
  3836. dot_begin();
  3837. dot_skip_over_ws();
  3838. break;
  3839. case 'I': // I- insert before first non-blank
  3840. dot_begin(); // 0
  3841. dot_skip_over_ws();
  3842. //**** fall through to ... 'i'
  3843. case 'i': // i- insert before current char
  3844. case KEYCODE_INSERT: // Cursor Key Insert
  3845. dc_i:
  3846. #if ENABLE_FEATURE_VI_SETOPTS
  3847. newindent = -1;
  3848. #endif
  3849. cmd_mode = 1; // start inserting
  3850. undo_queue_commit(); // commit queue when cmd_mode changes
  3851. break;
  3852. case 'J': // J- join current and next lines together
  3853. do {
  3854. dot_end(); // move to NL
  3855. if (dot < end - 1) { // make sure not last char in text[]
  3856. #if ENABLE_FEATURE_VI_UNDO
  3857. undo_push(dot, 1, UNDO_DEL);
  3858. *dot++ = ' '; // replace NL with space
  3859. undo_push((dot - 1), 1, UNDO_INS_CHAIN);
  3860. #else
  3861. *dot++ = ' ';
  3862. modified_count++;
  3863. #endif
  3864. while (isblank(*dot)) { // delete leading WS
  3865. text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
  3866. }
  3867. }
  3868. } while (--cmdcnt > 0);
  3869. end_cmd_q(); // stop adding to q
  3870. break;
  3871. case 'L': // L- goto bottom line on screen
  3872. dot = end_screen();
  3873. if (cmdcnt > (rows - 1)) {
  3874. cmdcnt = (rows - 1);
  3875. }
  3876. while (--cmdcnt > 0) {
  3877. dot_prev();
  3878. }
  3879. dot_begin();
  3880. dot_skip_over_ws();
  3881. break;
  3882. case 'M': // M- goto middle line on screen
  3883. dot = screenbegin;
  3884. for (cnt = 0; cnt < (rows-1) / 2; cnt++)
  3885. dot = next_line(dot);
  3886. dot_skip_over_ws();
  3887. break;
  3888. case 'O': // O- open an empty line above
  3889. dot_begin();
  3890. #if ENABLE_FEATURE_VI_SETOPTS
  3891. // special case: use indent of current line
  3892. newindent = get_column(dot + indent_len(dot));
  3893. #endif
  3894. goto dc3;
  3895. case 'o': // o- open an empty line below
  3896. dot_end();
  3897. dc3:
  3898. #if ENABLE_FEATURE_VI_SETOPTS
  3899. cmd_mode = 1; // switch to insert mode early
  3900. #endif
  3901. dot = char_insert(dot, '\n', ALLOW_UNDO);
  3902. if (c == 'O' && !autoindent) {
  3903. // done in char_insert() for 'O'+autoindent
  3904. dot_prev();
  3905. }
  3906. goto dc_i;
  3907. case 'R': // R- continuous Replace char
  3908. dc5:
  3909. cmd_mode = 2;
  3910. undo_queue_commit();
  3911. rstart = dot;
  3912. break;
  3913. case KEYCODE_DELETE:
  3914. if (dot < end - 1)
  3915. dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);
  3916. break;
  3917. case 'X': // X- delete char before dot
  3918. case 'x': // x- delete the current char
  3919. case 's': // s- substitute the current char
  3920. dir = 0;
  3921. if (c == 'X')
  3922. dir = -1;
  3923. do {
  3924. if (dot[dir] != '\n') {
  3925. if (c == 'X')
  3926. dot--; // delete prev char
  3927. dot = yank_delete(dot, dot, PARTIAL, YANKDEL, allow_undo); // delete char
  3928. #if ENABLE_FEATURE_VI_UNDO
  3929. allow_undo = ALLOW_UNDO_CHAIN;
  3930. #endif
  3931. }
  3932. } while (--cmdcnt > 0);
  3933. end_cmd_q(); // stop adding to q
  3934. if (c == 's')
  3935. goto dc_i; // start inserting
  3936. break;
  3937. case 'Z': // Z- if modified, {write}; exit
  3938. c1 = get_one_char();
  3939. // ZQ means to exit without saving
  3940. if (c1 == 'Q') {
  3941. editing = 0;
  3942. options.optind = cmdline_filecnt;
  3943. break;
  3944. }
  3945. // ZZ means to save file (if necessary), then exit
  3946. if (c1 != 'Z') {
  3947. indicate_error();
  3948. break;
  3949. }
  3950. if (modified_count) {
  3951. if (ENABLE_FEATURE_VI_READONLY && readonly_mode && current_filename) {
  3952. status_line_bold("'%s' is read only", current_filename);
  3953. break;
  3954. }
  3955. cnt = file_write(current_filename, text, end - 1);
  3956. if (cnt < 0) {
  3957. if (cnt == -1)
  3958. status_line_bold("Write error: %s", strerror(errno));
  3959. } else if (cnt == (end - 1 - text + 1)) {
  3960. editing = 0;
  3961. }
  3962. } else {
  3963. editing = 0;
  3964. }
  3965. // are there other files to edit?
  3966. j = cmdline_filecnt - options.optind - 1;
  3967. if (editing == 0 && j > 0) {
  3968. editing = 1;
  3969. modified_count = 0;
  3970. last_modified_count = -1;
  3971. status_line_bold("%u more file(s) to edit", j);
  3972. }
  3973. break;
  3974. case '^': // ^- move to first non-blank on line
  3975. dot_begin();
  3976. dot_skip_over_ws();
  3977. break;
  3978. case 'b': // b- back a word
  3979. case 'e': // e- end of word
  3980. dir = FORWARD;
  3981. if (c == 'b')
  3982. dir = BACK;
  3983. do {
  3984. if ((dot + dir) < text || (dot + dir) > end - 1)
  3985. break;
  3986. dot += dir;
  3987. if (isspace(*dot)) {
  3988. dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
  3989. }
  3990. if (isalnum(*dot) || *dot == '_') {
  3991. dot = skip_thing(dot, 1, dir, S_END_ALNUM);
  3992. } else if (ispunct(*dot)) {
  3993. dot = skip_thing(dot, 1, dir, S_END_PUNCT);
  3994. }
  3995. } while (--cmdcnt > 0);
  3996. break;
  3997. case 'c': // c- change something
  3998. case 'd': // d- delete something
  3999. #if ENABLE_FEATURE_VI_YANKMARK
  4000. case 'y': // y- yank something
  4001. case 'Y': // Y- Yank a line
  4002. #endif
  4003. {
  4004. int yf = YANKDEL; // assume either "c" or "d"
  4005. int buftype;
  4006. #if ENABLE_FEATURE_VI_YANKMARK
  4007. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  4008. char *savereg = reg[YDreg];
  4009. # endif
  4010. if (c == 'y' || c == 'Y')
  4011. yf = YANKONLY;
  4012. #endif
  4013. // determine range, and whether it spans lines
  4014. buftype = find_range(&p, &q, c);
  4015. if (buftype == -1) // invalid range
  4016. goto dc6;
  4017. if (buftype == WHOLE) {
  4018. save_dot = p; // final cursor position is start of range
  4019. p = begin_line(p);
  4020. #if ENABLE_FEATURE_VI_SETOPTS
  4021. if (c == 'c') // special case: use indent of current line
  4022. newindent = get_column(p + indent_len(p));
  4023. #endif
  4024. q = end_line(q);
  4025. }
  4026. dot = yank_delete(p, q, buftype, yf, ALLOW_UNDO); // delete word
  4027. if (buftype == WHOLE) {
  4028. if (c == 'c') {
  4029. #if ENABLE_FEATURE_VI_SETOPTS
  4030. cmd_mode = 1; // switch to insert mode early
  4031. #endif
  4032. dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
  4033. // on the last line of file don't move to prev line,
  4034. // handled in char_insert() if autoindent is enabled
  4035. if (dot != (end-1) && !autoindent) {
  4036. dot_prev();
  4037. }
  4038. } else if (c == 'd') {
  4039. dot_begin();
  4040. dot_skip_over_ws();
  4041. } else {
  4042. dot = save_dot;
  4043. }
  4044. }
  4045. // if CHANGING, not deleting, start inserting after the delete
  4046. if (c == 'c') {
  4047. goto dc_i; // start inserting
  4048. }
  4049. #if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
  4050. // only update status if a yank has actually happened
  4051. if (reg[YDreg] != savereg)
  4052. yank_status(c == 'd' ? "Delete" : "Yank", reg[YDreg], 1);
  4053. #endif
  4054. dc6:
  4055. end_cmd_q(); // stop adding to q
  4056. break;
  4057. }
  4058. case 'k': // k- goto prev line, same col
  4059. case KEYCODE_UP: // cursor key Up
  4060. case '-': // -- goto prev line
  4061. q = dot;
  4062. do {
  4063. p = prev_line(q);
  4064. if (p == begin_line(q)) {
  4065. indicate_error();
  4066. goto dc1;
  4067. }
  4068. q = p;
  4069. } while (--cmdcnt > 0);
  4070. dot = q;
  4071. if (c == '-') {
  4072. dot_skip_over_ws();
  4073. } else {
  4074. // try to stay in saved column
  4075. dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
  4076. keep_index = TRUE;
  4077. }
  4078. break;
  4079. case 'r': // r- replace the current char with user input
  4080. c1 = get_one_char(); // get the replacement char
  4081. if (c1 != 27) {
  4082. if (end_line(dot) - dot < (cmdcnt ? cmdcnt : 1)) {
  4083. indicate_error();
  4084. goto dc6;
  4085. }
  4086. do {
  4087. dot = text_hole_delete(dot, dot, allow_undo);
  4088. #if ENABLE_FEATURE_VI_UNDO
  4089. allow_undo = ALLOW_UNDO_CHAIN;
  4090. #endif
  4091. dot = char_insert(dot, c1, allow_undo);
  4092. } while (--cmdcnt > 0);
  4093. dot_left();
  4094. }
  4095. end_cmd_q(); // stop adding to q
  4096. break;
  4097. case 'w': // w- forward a word
  4098. do {
  4099. if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
  4100. dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
  4101. } else if (ispunct(*dot)) { // we are on PUNCT
  4102. dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
  4103. }
  4104. if (dot < end - 1)
  4105. dot++; // move over word
  4106. if (isspace(*dot)) {
  4107. dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
  4108. }
  4109. } while (--cmdcnt > 0);
  4110. break;
  4111. case 'z': // z-
  4112. c1 = get_one_char(); // get the replacement char
  4113. cnt = 0;
  4114. if (c1 == '.')
  4115. cnt = (rows - 2) / 2; // put dot at center
  4116. if (c1 == '-')
  4117. cnt = rows - 2; // put dot at bottom
  4118. screenbegin = begin_line(dot); // start dot at top
  4119. dot_scroll(cnt, -1);
  4120. break;
  4121. case '|': // |- move to column "cmdcnt"
  4122. dot = move_to_col(dot, cmdcnt - 1); // try to move to column
  4123. break;
  4124. case '~': // ~- flip the case of letters a-z -> A-Z
  4125. do {
  4126. #if ENABLE_FEATURE_VI_UNDO
  4127. if (isalpha(*dot)) {
  4128. undo_push(dot, 1, undo_del);
  4129. *dot = islower(*dot) ? toupper(*dot) : tolower(*dot);
  4130. undo_push(dot, 1, UNDO_INS_CHAIN);
  4131. undo_del = UNDO_DEL_CHAIN;
  4132. }
  4133. #else
  4134. if (islower(*dot)) {
  4135. *dot = toupper(*dot);
  4136. modified_count++;
  4137. } else if (isupper(*dot)) {
  4138. *dot = tolower(*dot);
  4139. modified_count++;
  4140. }
  4141. #endif
  4142. dot_right();
  4143. } while (--cmdcnt > 0);
  4144. end_cmd_q(); // stop adding to q
  4145. break;
  4146. //----- The Cursor and Function Keys -----------------------------
  4147. case KEYCODE_HOME: // Cursor Key Home
  4148. dot_begin();
  4149. break;
  4150. // The Fn keys could point to do_macro which could translate them
  4151. #if 0
  4152. case KEYCODE_FUN1: // Function Key F1
  4153. case KEYCODE_FUN2: // Function Key F2
  4154. case KEYCODE_FUN3: // Function Key F3
  4155. case KEYCODE_FUN4: // Function Key F4
  4156. case KEYCODE_FUN5: // Function Key F5
  4157. case KEYCODE_FUN6: // Function Key F6
  4158. case KEYCODE_FUN7: // Function Key F7
  4159. case KEYCODE_FUN8: // Function Key F8
  4160. case KEYCODE_FUN9: // Function Key F9
  4161. case KEYCODE_FUN10: // Function Key F10
  4162. case KEYCODE_FUN11: // Function Key F11
  4163. case KEYCODE_FUN12: // Function Key F12
  4164. break;
  4165. #endif
  4166. }
  4167. dc1:
  4168. // if text[] just became empty, add back an empty line
  4169. if (end == text) {
  4170. char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
  4171. dot = text;
  4172. }
  4173. // it is OK for dot to exactly equal to end, otherwise check dot validity
  4174. if (dot != end) {
  4175. dot = bound_dot(dot); // make sure "dot" is valid
  4176. }
  4177. #if ENABLE_FEATURE_VI_YANKMARK
  4178. if (dot != orig_dot)
  4179. check_context(c); // update the current context
  4180. #endif
  4181. if (!isdigit(c))
  4182. cmdcnt = 0; // cmd was not a number, reset cmdcnt
  4183. cnt = dot - begin_line(dot);
  4184. // Try to stay off of the Newline
  4185. if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
  4186. dot--;
  4187. }