vi.c 115 KB

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