vi.c 110 KB

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