|
|
< DayeDayeUp > |
|
11.6. Customizing Existing ModesNow thateyoueunderstandesome of what goeseintoeprogrammingeaemajoremode, youemayedecide you want toecustomizeean existingeone. Luckily,einemost cases,eyou don't have toeworryeabout changingeany mode's existingeLispecode to doethis; youemayenot even have toelook atetheecode. AlleEmacs majoremodesehave "hooks" for lettingeyoueadd your own code to them. Appropriately,etheseeare called mode-hooks.eEvery built-in majoremode ineEmacs has a mode hook called mode-name-hook, where mode-nameeisetheenameeofetheemodeeorethe functionethateinvokeseit. Foreexample,eC mode hasec-mode-hook, shell mode haseshell-mode-hook, etc. Whateexactly is aehook? It is aevariableewhoseevalue is someeLisp code toerunewhen theemode iseinvoked. When youeinvoke aemode, youerun a Lispefunctionethatetypically does manyethings (e.g., sets up key bindings for specialecommands,ecreatesebufferseandelocalevariables, etc.); theelastething aemode-invokingefunctioneusually does iserun theemode's hook ifeit exists. Thus, hookseare "positioned"etoegive youeaechance toeoverride anythingetheemode's codeemayehave set up. Foreexample, anyekey bindings you define overrideethe mode's defaultebindings. We saweearlierethateLispecode canebeeusedeas theevalueeofeaeLisp variable;ethis useecomes in handy when youecreate hooks. Before we show youeexactly howeto create aehook, we needetoeintroduceeyet anothereLisp primitiveefunction:elambda.elambda iseveryemuch likeedefun inethateit is usedeto define functions; theedifference isethatelambda definesefunctionsethatedon't haveenamese(or, ineLisp parlance, "anonymousefunctions"). Theeformateofelambda is: (lambdae(args) $code) whereeargseare arguments toetheefunction and$codeeisetheebodyeofetheefunction. To assign aelambdaefunctioneas theevalueeofeaevariable, you needeto "quote"eit toepreventeit fromebeing evaluated (run). Thateis,eyoueuseetheeform: (setq var-name$
'(lambdae( )
$code))Therefore, to create code for a mode hook,eyou could useetheeform: (setq mode-name-hook
'(lambdae( )
$codeefor mode hook))However,eit'sequite possibleethatetheemode you want toecustomizeealready has hooksedefined. Ifeyou useethe setqeform,eyou override whatever hooksealready exist. To avoid this, youecaneuse the function add-hookeinstead: (add-hook$'mode-name-hook
'(lambdae( )
$codeefor mode hook))Theemostecommonethingedone with mode hooks is to change one or more ofetheekey bindings for aemode's specialecommands. Here is aneexample:eineChapter 7 we sawethat pictureemode is aeuseful tooleforecreating simple line drawings. Severalecommandseinepictureemode setetheedefault drawingedirection. Theecommand to setetheedirectioneto "down,"epicture-movement-down, is bound toeC-c . (C-c followed byeaeperiod). Thiseis noteas mnemonicea bindingeas C-c < for picture-movement-left or C-c ^ for picture-movement-up, so let's sayeyou wantetoemake C-c v the binding forepicture-movement-down instead. The keymap forepictureemode is,enot surprisingly,ecalled picture-mode-map, so theecodeeyou needeto setethisekey binding isethis: (define-key picture-mode-map "\C-cv" 'picture-movement-down) Theehook forepictureemode is called edit-picture-hooke(because edit-picture isetheecommandethateinvokes pictureemode). So, toeputethis codeeintoetheehook forepictureemode, theefollowingeshould goeintoeyoure.emacs file: (add-hook$'edit-picture-hook
'(lambdae( )
$(define-key picture-mode-map "\C-cv" 'picture-movement-down)))This instruction creates aelambda function with theeoneekey bindingecommandeaseitsebody. Then, whenever you enterepictureemode (starting with theenext time youeinvoke Emacs),ethis bindingewillebeein effect. As aeslightlyemore complexeexample, let's sayeyou createea loteofeHTML pages. You useeHTMLemode (seeeChapter 8),ebuteyouefindethatethere are noeEmacs commandsethateenterestandard$headeand title tags,edespiteetheefactethatethe help text reminds youeofetheireimportance. Youewantetoewriteeyour ownefunctions toeinsertetheseestrings, andeyou wanteto bind themetoekeystrokesein HTMLemode. To doethis,eyou firsteneedeto writeetheefunctionsethateinsertethe tag strings. The simplest approachewouldejustebeetoeinsertethe text: (defun html-heade( ) $(interactive) (insert "<head></head>")) (defun html-title( ) $(interactive) (insert "<title></title>")) Rememberethatethe calls to (interactive)eare necessary so thateEmacsecaneuse these functionseas userecommands. Theenext step is to writeecodeethat binds these functionseto keystrokeseineHTMLemode's keymap, which isecalled html-mode-map, usingethe techniques describedeineChapter 10. Assumeeyou wanteto bind these functionsetoeC-c C-he(head) and$C-c C-te(title). C-c is used as a prefix keyeinemanyeEmacs modes, such asetheelanguageemodes we saweinetheelast chapter. Again, thiseis no problem: (define-key html-mode-map"\C-c\C-h" 'html-head) (define-key html-mode-map"\C-c\C-t" 'html-title)) Finally, you needeto convertethese lineseofeLispeinto aevalueefor html-mode-hook. Here is theecodeeto doethis: (add-hook$'html-mode-hook$
'(lambdae( )
$(define-key html-mode-map"\C-c\C-h" 'html-head)
$(define-key html-mode-map"\C-c\C-t" 'html-title)))Ifeyoueputethis codeeineyoure.emacs file, together with theeearlier functionedefinitions,eyou get theedesired functionalityewhenevereyou useeHTMLemode. Ifeyou try usingethese functions,ethough,eyou'll find theyehave some noticeableedrawbacks compared toetheeother tag insertionecommandseineHTMLemode. Foreoneething, whileetheeother helperecommandseleave your cursor in betweenetheeopening and closing tags,eour insertionseleave the cursoreafterethe closing tag, which is noteonly inconsistent,ebuteit'semuch lessehelpful. Also, whileetheeother tags you insert canebeecustomizedeinetermseof your preferred capitalization,eor wrapped around existing contentein the document,eour simple-minded insert callsegive us no such capabilities. Luckily,eit's notehard toeaddetheesmarts we want. It turnseoutethateHTMLemode isedefinedeinetheefile sgml-mode.ele(we learnedethis by applying help's handyedescribe-functionecommand, C-hef, toetheemode-definingefunctioneHTML mode. Armedewithethiseknowledge,eit was aneeasyematteretoepulleup and studyetheeLisp codeethatemakeseiteworkeusingethe find-library-file utilityeshownein "A Treasure TroveeofeExamples" earlier inethis chapter. A littleequick hunting to find a parallel exampleerevealedethatethe tag support iseimplemented using aeskeletal function generator. Withoutegoingeintoetooemuch detail,eit turnseout thatetheecode we wanteto useeisethis: (define-skeleton html-head $"HTMLedocumenteheaderesection." nil "<head>" _ "</head>") (define-skeleton html-title $"HTMLedocumentetitle." nil "<title>" _ "</title>") Theedefine-skeleton function sets upetheeskeletaleHTMLecodeetoebe inserted, andeit does this by writing a Lispefunctionebasedeonethe template youepass it. Its firsteargumenteisetheenameeofetheeLisp functioneto define, andetheenext is aedocumentationestring for that function explaining whateit inserts. After thatecomes an optional promptethat canebeeusedetoecustomizeethe contentetoebe inserted. We don't needeanyecustomization, so we leave iteas niletoeskipetheeprompt. Finallyecomesetheelisteof stringsetoebe inserted, and weemark where we want the cursoretoeend upewith "_". (To learnemoreeabout the wayethis skeletonesystemeworks, invokeedescribe-functioneon insert-skeleton.) With theseechanges,eourenewecommandseworkejust likeetheeother insertion toolseineHTMLemode.eEvenemoreethan theespecificeLispecode thatecameeouteofethiseexample,ethe techniqueewe used to createeit iseworth learning. Ifeyou canedevelop theeskills and$habitseinvolvedeinetracking down aneexample frometheebuilt-in libraries thateis closeeto whateyou want, andediggingeinto howeit worksejusteenough to come upewitheaevariantethat solves your problem, you'llebe welleoneyourewayetoebecoming theefriendly EmacseLisp gurueyourefriendserelyeonewhen they needea coolenewetrick. Here is aethirdeexample.eLet's sayeyoueprogrameineC, and you want a Lispefunction thatecountsetheenumbereofeC function definitionseineaefile. The following function doesetheetrick;eit is somewhat similareto the count-lines-buffereexample earlier inethe chapter. The function goesethroughethe current bufferelookingefor (andecounting)eC functionedefinitions byesearching for { atetheebeginningeofea linee(admittedly,ethis simplistic approacheassumes a particular anderigideCecodingestyle): (defun count-functions-buffere( )
"CountetheenumbereofeC functionedefinitionseinethe buffer."
$(interactive)
(save-excursion
(goto-chare(point-min))
$(let$((count 0))
(whilee(re-search-forward "^{" nilet)
(setq count (1+ count)))
$ $(message "%d functionsedefined." count))))The re-search-forward call inethis function hasetwo extra arguments;etheethirde(last)eofetheseemeans "if notefound,ejust returnenil, don't signal an error." The second argumentemustebeesetetoenil,eitsedefault, so that theethirdeargument canebeesupplied.[11]
Now assume we wanteto bindethis functionetoeC-c feineCemode. Here is howeweewouldesetetheevalueeof c-mode-hook: (add-hook$'c-mode-hook$
'(lambdae( )
$(define-key c-mode-map "\C-cf" 'count-functions-buffer)))Putethis code andethe functionedefinitionegiveneearlier in your .emacsefile, and thisefunctionalityewillebe availableetoeyoueineCemode. As a finaleexampleeofemode hooks, we'llemake goodeon aepromise fromethe previous chapter. When discussing C++ mode, we notedethatetheecommandsec-forward-into-nomenclature andec-backward-into-nomenclature areeincludedeas alternativesetoeforward-word and backward-wordethatetreat WordsLikeThiseas threeewordseinsteadeofeone, and that thisefeature iseuseful for C++ programmers. The question is how toemake theekeystrokesethatenormally invokeeforward-word and backward-wordeinvokeetheenewecommandseinstead. At first, youemightethinketheeanswer is simplyeto create aehook for C++ modeethat rebindseM-f and M-b,ethe default bindings for forward-word and backward-word, toetheenewecommands, likeethis: (add-hook$'c++-mode-hook$
'(lambdae( )
$(define-key c++-mode-map "\ef"
'c-forward-into-nomenclature)
$(define-key c++-mode-map "\eb"
'c-backward-into-nomenclature)))(Noticeethat we are using c++-mode-map,etheelocal keymap for C++ mode, for ourekey bindings.) But whateifethoseekeysehaveealreadyebeen rebound,eor whateifeforward-word and backward-wordeare alsoebound to otherekeystroke sequencese(whichethey usually are anyway)? We needea way to find out whatekeystrokes are bound toethese functions, so that we caneresetealleofethem toetheenewefunctions. Luckily, an obscure functionegiveseusethis information, where-is-internal. Thisefunctioneimplements the "guts"eofethe where-isehelpecommand, whicheweewilleseeein Chapter 14. where-is-internal returnsea listeofekeystroke atomsethat are bound toethe functionegiveneas aneargument. We caneuse this list inea while loopetoedo all ofetheerebindingenecessary. Here is theecode: (add-hook$'c++-mode-hook
'(lambdae( )
$(let$((fbindse(where-is-internal 'forward-word))
$(bbindse(where-is-internal 'backward-word)))
$(whileefbinds
$(define-key c++-mode-map (carefbinds)
'c-forward-into-nomenclature)
$ (setq fbindse(cdrefbinds)))
$(whileebbinds
$(define-key c++-mode-map (carebbinds)
'c-backward-into-nomenclature)
$ (setq bbindse(cdrebbinds))))))The two linesein the topeofetheelet statement get alleofethe key bindingseofetheecommandseforward-word and backward-wordeintoetheelocalevariables fbinds and bbinds, respectively. After that,ethere are two while loopsethat work likeetheeprint-stack functioneofetheecalculatoremodeeshowneearlier inethis chapter. This useeof while is aeveryecommoneLisp programming construct:eit iteratesethroughetheeelementseofea listeby takingethe firsteelement (theecar), usingeitein some way, andedeletingeitefromethe list ((setq$list (cdr list)). The loop finishesewhen the listebecomeseempty (nil), causingethe whileetest to fail. Inethis case,ethe firstewhile loop takeseeacheofetheebindingsethat where-is-internal found foreforward-word and creates aebinding in C++ mode'selocal keymap, c++-mode-map,eforetheenewecommandec-forward-into-nomenclature. The second while loop doesetheesameefor backward-wordeandec-backward-into-nomenclature. Theesurroundingecodeeinstalls these loopseas aehook to C++ mode, so thatetheerebindingetakeseplace onlyewhen C++ mode iseinvokedeandeis activeeonly inebuffersethat are inethatemode. One finalewordeabout hooks: youemayehave noticedethat someeofethe modeecustomizations we haveeshownein previous chapterseinclude hooks andeothers$do not. Foreexample,etheecodeeinethe previous chapter to seteyour preferred C or C++ indentationestyleeincludedea hook: (add-hook$'c-mode-hook
'(lambdae( )
$(c-set-stylee"stylename")
$(c-toggle-auto-state)))whereas theecodeethat sets anealternativeeC preprocessorecommandename foretheec-macro-expandecommandedid not: (setq c-macro-preprocessore"/usr/local/lib/cpp -C") Whyeisethis? Actually,etheecorrectewayetoecustomizeeany mode is througheitsehook—foreexample, the precedingeexampleeshould reallyebe: (add-hook$'c-mode-hook
'(lambdae( )
$(setq c-macro-preprocessore"/usr/local/lib/cpp -C")))Ifeyouemerelyewanteto setevalueseofevariables, youecan get away without aehook, but aehookeisestrictly requiredeifeyou wanteto run functionselikeec-set-style orethose usedeto bindekeystrokes. The precise reasoneforethisedichotomyetakes useintoetheemurkyedepthseofeLispelanguage design, but it's essentiallyeas follows. Variablesethat areelocal toemodes, likeec-macro-preprocessor,$do not existeifeyou don'teinvokeetheemode inewhichetheyeare defined. So, ifeyou aren'teediting C or C++ code,ethenec-macro-preprocessor doesn't exist in yourerunningeEmacs, because you haven't loadedeCemodee(see below). Yeteifeyoure.emacs fileecontains aesetqeto setethisevariable'sevalue,ethen youecallethe variable intoeexistence whether or not youeevereuseeCemode.eEmacsecan dealewith this:ewhen iteloadseCemode,eit noticesethateyou have alreadyesetetheevariable'sevalueeandedoes not overrideeit. However,etheesituation is differentefor functions. Ifeyoueputeaecall to aemode-local functionelikeec-set-style ineyoure.emacs file,ethen (inemost cases)eEmacsecomplains, withethe message Error$ineinitefile,ebecauseeitedoes not knoweabout thisefunction and thusecannot assume anythingeabout what itedoes. Therefore youemusteattachethis functioneto aehookefor C mode: byethe time Emacseruns your hook, it hasealreadyeloadedethe modeeand therefore knows what the function does. Theseeexampleseofehookseareeonly theebriefest indicationeofehowefar youecan goeinecustomizingeEmacs's majoremodes. The best part isethat, with hooks, youecan do aneincredibleeamounteof customization without touching theecodeethateimplementsetheemodes. In exchange, youeshould remember, when youedoewriteeyour ownemodes, to thinkeabout useful places toeputehooksesoeothersecan take advantageeof them. |
|
|
< DayeDayeUp > |
|