Forum Programmation.autre [Emacs.Lisp] Définition des touches pour un mode majeur

Posté par  (site web personnel) .
Étiquettes :
0
11
sept.
2012

Bonsoir à tous,

Je me suis lancé il y a environ une semaine dans l'apprentissage du langage de programmation Lisp.
Je souhaite créer un mode majeur qui me permettrait de lancer mes applications à l'aide d'un menu principal. J'aimerais que ce menu ressemble à celui de mu4e :

Titre de l'image

Pour écrire le code de mon mode majeur je me suis donc inspiré de celui de mu4e et de text-mode.el
Bien évidement je me documente sur le Wiki d'Emacs et le site gnu.org à l'aide de "An Introduction to Programming in Emacs Lisp".

;
    (defconst rct-mode-buffer-name "*rct-main*"
      "*internal* Name of the rct main view buffer.")  
;    
; let the user have the possibility to run his hown code
    (defcustom rct-mode-hook nil
      "Normal hook run when entering Text mode and many related modes."
      :type 'hook
      :group 'data)
;    
; Keymap
    (defvar rct-mode-map 
      (let ((map (make-sparse-keymap)))
        (define-key map (kbd "C-c m") 
            (lambda() (interactive) (message "C-c m pressed")))
        (define-key map (kbd "<f4>")
            (lambda() (interactive) (message "<f4> pressed")))
        (define-key map (kbd "<f5>") 
            (lambda() (interactive) (message "<f5> pressed")))
        map )
      "Keymap for `rct-mode'.")
;    
; h1 first header in blue
    (defface h1
      '((t :inherit default 
           (:background "blue")
           ))
      "Face for a header without any special flags."
      :group 'rct-faces)
    ;
    (define-derived-mode rct-mode special-mode "rct:main"
      "Major mode for remote connections
    \\{rct-main-mode-map}."
      (setq debug-on-error t)
      (use-local-map rct-mode-map)
      (run-hooks 'rct-mode-hook)
      (main-menu))
;
; Main Menu
    (defun main-menu ()
      "This function display the main menu of the RCT mode."
      (with-current-buffer (get-buffer-create rct-mode-buffer-name)
        (erase-buffer)
        (insert
         "\n\n\n\n"
         (propertize "~~ Welcome to the Remote Connection Tool for Emacs ~~" 'face 'h1)
         "\n\n"
         "Main commands"
         "\n\n"
         "\t[Q]  To quit the RCT-mode"
         "\n\n"
         "Section 1"
         "\n\n"
         "\t[F2] To Start XXXX\n"
         "\t[F3] To Start YYYYY\n"
         "\n"
         "Section 2"
         "\n\n"
         "\t[F4] To Start ZZZZZ\n"
         "\n\n"
         ); end-of insert
        );end-of with-current-buffer
      ;);end-of let
      (switch-to-buffer rct-mode-buffer-name)
      (toggle-read-only t)
     );end-of main-menu
    ;
    (provide 'rct-mode)

Je rencontre actuellement deux principaux problèmes avec mon code :

  • lorsque je lance mon mode-majeur la définition des touches n'est pas prise en compte et n'est pas chargée par la fonction (use-local-map). A chaque pression de la touche fx ou même C-c m les messages " is not defined"_ ou bien "no kbd macro is defined" s'affichent dans le mini-buffer,
  • le texte auquel j'ai attribué une "face" dite "h1" pour apparaître en bleu est affiché avec la couleur par défaut : en noir.

Je vous remercie par avance de votre aide.

  • # editeur avec coloration

    Posté par  . Évalué à 2.

    avec la coloration on voit, en tout cas dans le code publié ici qu'il manque des ' par endroit

    ex :

    […](run-hooks 'rct-mode-hook)
    (main-menu))

    • [^] # Re: editeur avec coloration

      Posté par  (site web personnel) . Évalué à 1.

      A quel niveau exactement ? Car dans la ligne (run-hooks 'rct-mode-hook) pour moi il ne manque pas de ' . Je regarde à nouveau une autre documentation et je remarque bien les deux lignes :

          (use-local-map text-mode-map)
          (run-mode-hooks 'text-mode-hook)
      
      
    • [^] # Re: editeur avec coloration

      Posté par  (site web personnel) . Évalué à 1.

      Non, c'est syntaxiquement correct en LISP.

      • [^] # Re: editeur avec coloration

        Posté par  (site web personnel) . Évalué à 0.

        Où est donc l'erreur ?
        J'ai beau chercher, lire la doc je ne comprends pas pourquoi mes touches sont considérées comme "non définies".

      • [^] # Re: editeur avec coloration

        Posté par  . Évalué à 2.

        autant pour moi, faut dire que c'est un language quand meme fatiguant pour les yeux
        compter les parentheses pour savoir si on en a oublié une,
        utiliser les ' simples

        (ils ont surement leur raison d'etre, mais je ne programme pas en LISP, aussi ca me semblait bizarre)

        du coup je ne sais pas, y a pas un debugger avec LISP ? une option en ligne de commande pour le rendre plus bavard ?

  • # Le mode est changé dans le buffer dans lequel tu as lancé ta commande

    Posté par  . Évalué à 2.

    Je ne sais pas si tu cherches toujours, mais j'ai peut-être ta réponse.

    Quand tu lance ton mode, c'est le buffer dans lequel tu as lancé ta commande qui est pris en compte, pas celui que tu crées après.

    J'ai regardé un peu le code de mu4e, et ce que fait le gars, c'est qu'il crée d'abord la fonction mu4e qui va s'occuper d'initialiser tout un tas de trucs dont te créer ton buffer et lancer le mode adéquat dedans.

    Dans ton cas, il faut que tu crée une fonction toi-même qui va appeler main-menu et lancer ton mode après :

    (defun rct ()
      (main-menu)
      (rct-mode))
    
    

    Ou un truc du genre.
    Par contre, dans la définition de ton mode, il ne faut pas que tu appelle main-menu, bien sûr.

    • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

      Posté par  (site web personnel) . Évalué à 1.

      Merci Renaud !

      Oui tu as raison c'est ce que j'ai fait également. En même temps j'avais demandé un peu d'aide à la liste help-gnu-emacs et quelqu'un m'a expliqué un peu mes erreurs.

      Ce que j' ai compris c'est que si j'utilise la fonction (define-derived-mode) je ne dois pas de dans faire appel aux fonctions (add-hooks), (run-hooks) car elles sont déjà gérées par (define-derived-mode). En ce qui concerne l'appel du menu il doit se faire comme tu l'a précisé dans une fonction à part.

      Maintenant lors de l'exécution de mon mode majeur mes touches sont définies cependant mon menu ne s'affiche plus. Je reste sur le buffer d'accueil *GNUS emacs*. Pourtant dans la fonction affichant le menu j'ai bien fait appel à la fonction (pop-to-buffer buf) pour changer de buffer.

      • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

        Posté par  . Évalué à 2.

        Euh là sans le code, je ne peux pas vraiment t'aider. pop-to-buffer marche comme il faut chez moi…

        Sinon, je te conseille d'aller regarder la doc de Edebug, le debugger interne à emacs.

        • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

          Posté par  (site web personnel) . Évalué à 0.

          Oui sans le code c'est difficile …

          Voici le code:

          (defconst rct-mode-buffer-name "*rct-main*"
            "*internal* Name of the rct main view buffer.")  
          
          ; Keymap
          (defvar rct-mode-map
             (let ((my-map (make-sparse-keymap)))
                  (suppress-keymap my-map)
                  (define-key my-map "q" 'quit-window)
                  (define-key my-map (kbd "C-c m") 
                     (lambda() (interacive) (message "\C-\c m pressed")))
                   (define-key my-map (kbd "<f3>")
                  (lambda() (interactive) (message "\<f3\> pressed")));
                   (define-key my-map (kbd "<f4>")
                  (lambda() (interactive) (message "\<f4\> pressed")))
                   (define-key my-map (kbd "<f5>") 
                  (lambda (interactive) (message "\<f5\> pressed")))
                        my-map)
                  "Local keymap")
          
          ;; h1 first header in blue
          (defface h1
            '((t :inherit default 
                 (:background "blue")
              ))
            "Face for a header without any special flags."
            :group 'rct)
          
          (define-derived-mode rct-mode special-mode "rct:main"
            "Major mode for remote connections
          \\{rct-main-mode-map}."
            ; debug is activated 
            (set (make-local-variable 'debug-on-error) t)
            )
          
          ; Main Menu
          (defun main-menu ()
            "This function display the main menu of the RCT mode."
           (let ((buf (get-buffer-create rct-mode-buffer-name))
                 (inhibit-read-only t))
             (with-current-buffer buf
               (erase-buffer)
               (insert
                "\n\n\n\n"
                (propertize "~~ Welcome ~~" 'face 'h1)
                "\n\n"
                "Main commands"
                "\n\n"
                "\t[Q]  To quit the RCT-mode"
                "\n\n"
                "TEXT"
                "\n\n"
                "\t[F2] AAAAAAAA\n"
                "\t[F3] BBBBBBBB\n"
                "\n"
                "TEXT"
                "\n\n"
                "\t[F4] To Start XXXX\n"
                "\n\n"
                ); end-of insert
               ;(rct-mode)
               (pop-to-buffer buf)
               );end-of with-current-buffer
             );end-of let
            ;(toggle-read-only t)
           );end-of main-menu
          
          (defun rct() 
            ;; The menu have to be in a separate function.
            (rct-mode)
            (main-menu)
            )
          
          
          (provide 'rct-mode)
          
          
          • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

            Posté par  . Évalué à 1.

            Alors déjà, si t'appelle la fonction qui change le buffer courant avant d'appeler main-menu qui va créer ton buffer, tu te retrouves avec le même problème qu'avant, c'est le buffer dans lequel tu lance ton mode qui va être changé.

            Ensuite, le grand principe de (with-current-buffer …), c'est que quand tu sors du scope de cette macro, tu retrouves l'état que tu avais avant, c'est-à-dire que la suite va s'exécuter dans le buffer dans lequel tu lance ton mode.

            Si tu corriges ces deux erreurs, tu te retrouves avec ton buffer ayant le bon mode.

            • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

              Posté par  (site web personnel) . Évalué à 0.

              Je ne suis pas sûr d'avoir bien compris.
              Voici ce que j'ai modifié :

              (defun main-menu ()
                "This function display the main menu of the RCT mode."
               (let ((buf (get-buffer-create rct-mode-buffer-name))
                     (inhibit-read-only t))
                 (with-current-buffer buf
                   (erase-buffer)
                   (insert
                      "text pour l\'insert"
                    ); end-of insert
                   (rct-mode)
                   (pop-to-buffer buf)
                   );end-of with-current-buffer
                 );end-of let
               );end-of main-menu
              
              (defun rct()
                (main-menu))
              
              

              J'ai également essayé :

              (defun main-menu ()
                "This function display the main menu of the RCT mode."
               (let ((buf (get-buffer-create rct-mode-buffer-name))
                     (inhibit-read-only t))
                 (with-current-buffer buf
                   (erase-buffer)
                   (insert
                      "text pour l\'insert"
                    ); end-of insert
                   (pop-to-buffer buf)
                   );end-of with-current-buffer
                 );end-of let
               );end-of main-menu
              
              (defun rct()
                (main-menu)
                (rct-mode))
              
              
              • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

                Posté par  . Évalué à 1.

                Ta dernière fonction rct est bien, mais c'est le pop-to-buffer qui est mal placé.
                Il faut que tu le sorte du bloc (with-current-buffer …), sinon il n'aura pas d'effet.

                • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

                  Posté par  (site web personnel) . Évalué à 0.

                  En mettant le (pop-to-buffer buf) juste après le (with-current-buffer buf) comme ci-dessous, le menu ne s'affice toujours pas.

                  (defun main-menu ()
                    "This function display the main menu of the RCT mode."
                   (let ((buf (get-buffer-create rct-mode-buffer-name))
                         (inhibit-read-only t))
                     (with-current-buffer buf
                       (erase-buffer)
                       (insert
                        "\n"
                        ); end-of insert
                       );end-of with-current-buffer
                     (pop-to-buffer buf)
                     );end-of let
                   );end-of main-menu
                  
                  (defun rct() 
                    (main-menu)
                    (rct-mode))
                  
                  
                  • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

                    Posté par  . Évalué à 1.

                    Bah écoute, moi j'ai pas de problème…

                    Y'a quand même un truc qui manque dans ta fonction rct, c'est l'instruction (interactive) au début pour pouvoir la lancer avec M-x ou avec un raccourcis

                    • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

                      Posté par  (site web personnel) . Évalué à 0.

                      Renaud, est-ce que mon code fonctionne chez toi?

                      Je ne pense pas que rajouter (interactive) dans ma fonction rct() soit nécessaire car si j'utilise define-derived-mode je suppose que l'appel de cette fonction est déjà gérée tout comme le sont (add-hooks) et (use-local-map) par exemple. Depuis le début je fonctionne sans le (interactive) et lors du premier lancement d'Emacs je peux bien lancer mon mode majeur par M-x rct-mode.

                      Je viens de faire un saut à nouveau sur le site de mu4e pour relire son code et dans le fichier mu4e.el on voit bien que dans sa fonction appelant le define-derived-mode il fait bien appel, comme tu le préconise, à la fonction (interactive). Donc ma question est si je n'utilise pas la fonction (interactive) pour quelle(s) raison(s) puis-je quand même démarrer mon mode avec M-x ?

                      Ce qu'il se passe lorsque je démarre mon mode c'est que je reste sur le buffer présent et ne passe pas au buffer du mode "rct". Je vois bien dans la barre que le nom du mode actuel est "rct:main" (le buffer reste "GNU Emacs). De toute façon mes touches fonctionne donc cela veux bien dire aussi quelque part que mon mode est correctement lancé.

                      Je voudrais à nouveau préciser que je débute tout juste en LISP et aussi à utiliser (re-découvrir) Emacs.

                      ps: j'ai également essayé en rajoutant la fonction (interactive) dans ma fonction rct() mais je ne note aucun changement. J'ai aussi affiché les buffers ouverts par (C-x b + <TAB>) et seulement deux buffers sont "ouverts" : Message et scratch. J'en déduis donc que mon buffer rct-main est créé mais qu'il n'efface pas le contenu du buffer courant (d'ailleurs ni son nom n'est affiché).

                      • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

                        Posté par  (site web personnel) . Évalué à 0.

                        Je viens de trouver quelque chose d'intéressant. Si je lance mon mode par M-x rct alors là mon mode se lance dans un buffer en dessous du buffer GNU Emacs et affiche mon menu (donc ma fenêtre est coupée en deux, ce que au passage je ne souhaite pas). Alors que si je fais M-x rct-mode, qui est aussi disponible, mon mode majeur se lance dans le buffer actuel sans afficher le menu principal. Il y a confusion. j'ai donc rct et rct-mode. Puis-je en avoir qu'un seul?

                        • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

                          Posté par  (site web personnel) . Évalué à 0.

                          Pour que la fenêtre ne se coupe pas en deux je dois utiliser switch-to-buffer et non pas pop-to-buffer

                          NB: je ne parviens plus à éditer mes messages

                          • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

                            Posté par  (site web personnel) . Évalué à 0. Dernière modification le 14 septembre 2012 à 11:58.

                            Ah mais c'est gênant ça ! Je suis en train de modifier un message et au bout d'un moment je ne peux plus poster mon message car je n'ai plus le droit de le modifier. J'ai un message "Page interdite - Vous ne passerez pas" qui s'affiche.

                            Les problèmes qui me restent à régler:

                            1. avoir un seul mode rct-mode et non pas rct en plus.

                            2. le texte désiré en bleu apparaît en noir alors que j'ai défini une 'face particulière

                            3. lorsque j'exécute un programme extérieur je perds la main sur Emacs et je ne peux pas faire autre chose ensuite. J'utilise (shell-command cmd) pour exécuter un programme.

                            • [^] # Re: Le mode est changé dans le buffer dans lequel tu as lancé ta commande

                              Posté par  . Évalué à 2. Dernière modification le 14 septembre 2012 à 17:39.

                              J'ai compris d'où venait ton problème. Il faut que tu lance ton mode avec M-x rct, pas M-x rct-mode. Et c'est parfaitement normal.
                              En gros, ce que tu es en train de programmer n'est pas un mode majeur standard comme pourraient l'être c++-mode ou org-mode. Tu es en train de coder une sorte de programme complet qui tourne dans emacs.

                              Et pour cela, tu as besoin d'une fonction principale qui s'occupe de gérer le ou les modes utilisés par les buffers de ton programme. En l'occurence tu n'en as qu'un, mais le principe reste le même, surtout si tu veux utiliser un buffer qui n'est pas lié à un fichier.

                              Ce qui fait que tu dois lancer ton programme à l'aide de M-x rct, qui va créer le buffer dont tu as besoin, et qui va spécifier le mode majeur dans lequel ce buffer devra être.

                              Ce qu'on appelle « mode majeur » n'est que la définition du comportement principal d'un buffer quel qu'il soit.

                              Pour la couleur de ton texte, je suis désolé, je n'y connais rien en faces. À vrai dire, je suis moi-même un débutant en Emacs Lisp et je n'ai que peu d'expérience en création de mode. Je m'intéresse à la doc interne à emacs et je suis pas mal de blogs sur le sujet mais c'est à peu près tout.

                              Pour l'exécution d'un programme externe, si tu veux le lancer de manière asynchrone (sans perdre la main), tu peux utiliser la fonction (async-shell-command), c'est dans la doc de (shell-command)…

                              Et oui, si tu ne veux pas avoir un buffer à coté, c'est (switch-to-buffer) qu'il faut utiliser. C'est surtout une préférence quant au comportement de ton programme. C'est à toi de choisir ce que tu préfères

  • # [Résolu Emacs.lisp : Définition des touches pour un mode majeur]

    Posté par  (site web personnel) . Évalué à 1.

    J'ai décidé de séparer mon programme en plusieurs parties :

    • un fichier pour les variables globales
    • un fichier décrivant les différentes fonctions que je vais appeler lorsque j'appuie sur une touche
    • un fichier contenant le menu principal.

    J'ai bien résolu mon problème d'affichage du menu ainsi que la gestion des touches de fonction .

    Pour exécuté un programme externe j'utilise le code suivant :

    (start-process <nom du processus>
             (get-buffer-create <nom du buffer>)
                 <nom + chemin vers la commande>
                 <arg1>
                 <argn>
                 ))
    
    

    En ce qui concerne l'utilisation de la couleur pour le texte j'ai créé une face comme ceci :

    ;  le texte dont la 'face sera 'h1-face sera en bleu et en gras.
    (defface h1-face 
      '((t (:weight extra-bold 
            :foreground "blue")
           )) 
      "information sur la 'face")
    
    

    Pour appliquer cette 'face au texte j'utilise la fonction propertize :

    (propertize "Texte" 'face 'h1-face)
    
    

    Je vous remercie pour vos commentaires qui m'ont bien aidé !

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.