این ایمکس دوست داشتنی (استفاده از ایمکس به عنوان ترمینال!)

·

8 min read

خوب همانطور که می‌دانید من خیلی خیلی وابسته به ترمینال‌ام هستم. خصوصا این که با هزار ترفند روی i3 یک چیزی شبیه Quake درست کردم تا ترمینال‌ام با کلید F12 در دسترس باشد. به هر حال‌، این در دسترس بودن‌، وسوسه‌ام کرد که همین کار را با ایمکس هم انجام دهم. چند روز گذشته همچین وضعیتی بود. با F12 ترمینالم را داشتم و با Meta+F12 ویرایشگرم را. تا حدی قابل قبول بود. ولی خوب باز آن‌قدر‌ها که باید به دل نمی‌نشست.

مهم‌ترین دلیلش این که به دلیل کوچک بودن صفحهٔ مانیتورم‌، با چند پنجرهٔ باز در آن واحد‌، خیلی شلوغ‌کاری می‌شد. این بود که امروز تصمیم گرفتم تا جای ممکن‌، اجزای اضافی را حذف کنم. و خوب وقتی پای ایمکس در میان باشد‌، هر ابزار دیگری اضافه است D:

در دسترس بودن همیشگی ایمکس چند مزیت عمده دارد. اول این که می‌توانم هر وقت که خواستم‌، با org-mode و آن قابلیت Capture خیلی خیلی به درد بخورش‌، هر Note, Link یا TODO‌ای که به ذهنم رسید را سریع ثبت کنم که از دستم نرود (راجع به این یکی خیلی حرف خواهیم زد. خیلی هم زود این کار را می‌کنیم). دومندش را هم نمی‌شود این‌جا گفت‌، باید اول راجع به یک چیز دیگر بگویم‌!‌

خوشبختانه ایمکس قابلیت اجرای پوستهٔ فرمان را در خودش دارد. آن هم به هزار و اندی روش مختلف‌! من هنوز راجع به تفاوت‌ها و مزیت‌های این پوسته‌ها زیاد نمی‌دانم. ولی فعلا می‌خواهم بستهٔ multi-term را نصب و تجربه کنم. این بسته در واقع یک سری امکانات اضافه را نسبت به term که همراه خود ایمکس نصب می‌شود به همراه دارد. خوب ما هم فعلا تستش می‌کنیم تا ببینیم چه می‌شود.

package-install multi-term

و حالا‌ هم با دستور زیر اجرایش می‌کنم:

M-X multi-term

یک کمی بالا و پایینش می‌کنم. همه چیز خوب کار می‌کند‌، جز این که قابلیت تکمیل خود‌کار حیاتی ZSH را از دست داده‌ام. چرا؟ نمی‌دانم ولی بیایید ببینیم این کلید چه فرمانی را اجرا می‌کند:

C-h k TAB

ایمکس می‌گوید که دارد yas-expand-from-trigger-key را اجرا می‌کند که مربوط به بستهٔ Yasnippet است که برای دسترسی سریع به قالب‌های آماده استفاده می‌کنم. (اگر نمی‌دانید چیست نگران نباشید‌، راجع به این هم خیلی زود می‌نویسم). خوب‌، من Yasnippet را به صورت سراسری اجرا کرده‌ام. حالا چطور می‌توانم فقط برای این مد خاص خاموشش کنم؟ (عملا نیازی به قالب آماده در شل نیست D:). کمی جستجو در اینترنت و نتیجه می‌گیرم که اضافه کردن یک hook به مد term بهترین راه‌کار است. منظور این که به مد term بگوییم‌، وقتی اجرا شدی خودت Yasnippet را خاموش کن. حتما می‌پرسید چرا مد term و نه multi-term! جوابش را قبلا هم گفتم‌، multi-term یک جور بستهٔ تکمیلی برای term است و بس. اصل کار را term-mode انجام می‌دهد. پس داریم:

(add-hook 'term-mode-hook (lambda()
                        (yas-minor-mode -1)))

و تمام. حالا یک‌بار دیگر multi-term را تست می‌کنیم. عالی است. دقیقا رفتار یک Terminal emulator را انجام می‌دهد. حتی htop و ranger هم به خوبی کار می‌کنند.

ایمکس و i3

حالا که همه چیز خوب کار می‌کند بیایید پنجرهٔ ایمکس را به صورت Scratchpad به i3wm اضافه کنیم تا بتوانیم با کلید F12 به آن دسترسی داشته باشیم:

floating_minimum_size 75 x 50
floating_maximum_size -1 x -1

for_window [instance="emacs"] move to scratchpad, border 1pixel
bindsym F12 [instance="emacs"] scratchpad show, move position 110px 0px

exec --no-startup-id emacs --no-splash -g 125x30

این خطوط همهٔ کاری است که باید انجام دهید. در دو خط اول یک حداقل/حداکثر برای سایز پنجره‌های float روی i3wm تعیین می‌کنیم. با فرستادن ‎-1 به عنوان مختصات حداکثر پنجره‌، در واقع به i3wm حالی می‌کنیم که کاری به کار سایز پیش‌فرض خود برنامه نداشته باشد. متاسفانه نمی‌توانم با تغییر سایز‌های i3 کنار بیایم‌، هیچ‌وقت هماهنگ نیستند. این است که ترجیح می‌دهم اندازهٔ پنجره با خود ایمکس تنظیم کنم.

در خط سوم پنجرهٔ emacs را به scratchpad می‌فرستم و با خط چهارم کلید F12 را برای toggle کردنش تعیین می‌کنم. در آخر هم ایمکس را با آرگومان ‎-g برای تعیین سایز و ‎--no-splash اجرا می‌کنم. این کار را می‌توان از داخل خود فایل کانفیگ ‎.emacs هم انجام داد. ولی تا آن کاملا لود شود‌، هزار و یک جور تغییر سایز می‌بینیم که برای در رفتن از این وضعیت تغییر اندازه را به زمان اجرای برنامه می‌سپاریم.

آن ‎--no-splash هم دلیل دارد که در ادامه می‌فهمیم.

https://cdn.hashnode.com/res/hashnode/image/upload/v1672742929177/FhEsX4Szk.png

اجرای پیش‌فرض ترمینال در ایمکس

خوب حالا که این تنظیمات را انجام دادیم می‌بینیم که بعد از هر بار روشن شدن سیستم باید خودمان multi-term را اجرا کنیم که اصلا خوب نیست. هر چه باشد عمدهٔ استفادهٔ ما روی ترمینال است. اولین کار این است که از دست آن Splash screen ایمکس خلاص شویم که خوب در مرحلهٔ قبل انجامش دادیم.و حال این خط را به ‎.emacs اضافه می‌کنیم:

(setq initial-major-mode 'multi-term)

این متغیر initial-major-mode یک مد پیش‌فرض را برای پنجرهٔ اولیه ایمکس تعیین می‌کند که ما با قرار دادن multi-term به عنوان مقدارش‌، به ترمینال‌مان می‌رسیم.

تغییر سایز پنجره

قرار که نیست همیشه در آن سایز کوچک به کارمان برسیم. مثلا برای نوشتن همین پست من به یک اندازهٔ بزرگ نیاز دارم. اما خارج کردن ایمکس از حالت floating روی ایمکس‌، آن را از کلید‌های میانبر تعریف کرده‌مان جدا می‌کند (کلید برای دوباره فرستادن این پنجره به scratchpad تعریف نکرده‌ایم). اولین چیزی که به نظرمان می‌رسد این است که یک کلید هم برای فرستادن پنجره به Scratchpad تعیین کنیم‌، ولی به نظرم یک کمی کثیف کاری می‌شود. بهتر است دنبال راه حل بهتری بگردیم.

ایمکس به صورت پیش‌فرض از Fullscreen پشتیبانی می‌کند. هرچند کلی تنظیمات لازم دارد که بستهٔ Fullscreen-mode برای‌مان انجامش می‌دهد. کافیست بسته را به صورت زیر نصب کنید:

package-install fullscreen-mode

و بعد از اضافه کردن (follscreen-mode) به ‎.emacs آن را با زدن کلید F11 فراخوانی کنیم. عالی است نه؟

حالا خیلی راحت تمامی امکانات پیش‌فرض ایمکس را همراه ترمینال‌مان داریم. خیلی راحت می‌توانیم خروجی‌ها را در فایل‌ها سیو کنیم و یا از فعالیت‌مان یک History کامل بسازیم.

نکته ۱: این یک پست تجربی است. هر وقت چیز جدیدی در این زمینه یاد گرفتم‌، این پست را ویرایش می‌کنم. نکته ۲: برای زدن C-c در ایمکس‌، باید دوبار آن را بزنید. امکانش هست که آن را تغییر دهیم‌، ولی فعلا من به آن دستی نزده‌ام.

ویرایش اول

خوب بعد از تقریبا دو روز سر و کله زدن با این ترمینال‌، به نکاتی بر خوردم که به نظرم خوب است در این‌جا مستند شود:

ایجاد ترمینال جدید

اول این که ممکن است شما هم مثل علیرضا عزیز که در بخش کامنت‌ها ذکر کرد‌، با ایجاد یک ترمینال جدید مشکل داشته باشید. خوب این کار تنها با صدا زدن دوبارهٔ multi-term قابل انجام است. همان‌طور که قبلا هم در این مورد صحبت کردیم‌، می‌توانید برای این کار یک کلید میانبر تعریف کنید. اما اگر مثل من از ergoemacs-mode استفاده می‌کنید‌، کافیست برای ایجاد یک ترمینال جدید از C-n بهره بگیرید ;-) (این کلید‌ها به طور پیش‌فرض یک بافر در حالت lisp-mode ایجاد می‌کنند که با تغییر متغیر initial-major-mode ما آن را به یک شل جدید نسبت می‌دهیم).

تغییر خودکار مسیر ایمکس همزمان با ترمینال

وقتی با ترمینال وارد یک مسیر جدید می‌شویم‌، و تصمیم داریم یک فایل را با ایمکس در آن‌جا ویرایش کنیم‌، بدیهی است که کلید C-o (یا C-x C-f پیش‌فرض) باید از آن مسیر اجازهٔ جستجوی فایل را بدهند. ولی به صورت پیش‌فرض به مسیر خانگی‌مان بر می‌گردند. برای حل این مشکل از کد‌هایی که در این‌جا پیدا کردم استفاده می‌کنیم:

(defadvice term-send-input (after update-current-directory)
       "Update the current directory."
       (let\* ((pid (process-id (get-buffer-process (current-buffer))))
          (cwd (file-truename (format "/proc/%d/cwd" pid))))
       (cd cwd)))
(ad-activate 'term-send-input)

(defadvice term-send-raw (after update-current-directory)
       "Update the current directory."
       (let\* ((pid (process-id (get-buffer-process (current-buffer))))
           (cwd (file-truename (format "/proc/%d/cwd" pid))))
       (cd cwd)))
(ad-activate 'term-send-raw)

خوب سواد من نسبت به این توابع خیلی کم است. اما اول این که با defadvice دو تابع term-send-input/raw را به قولی نصیحت می‌کنیم که هر بار با تغییر مسیر ترمینال‌، از مسیر ‎proc آخرین مسیر حاضر را را دریافت کند و به آن وارد شود. دستور cd استفاده شده‌، مربوط به ایمکس است و ربطی به لینوکس ندارد. ولی بدیهی است که این تابع فقط روی سیستم‌های یونیکس بیس کار خواهد کرد.

جانشینی کلید‌های میانبر

بعد از مدتی کار متوجه می‌شوید میانبر‌های ترمینال‌تان به درستی عمل نمی‌کنند. مثلا C-r که در تاریخچهٔ ترمینال جستجو می‌کند‌،‌ درون ایمکس‌، خود بافر را جستجو می‌کند. این است که کلید‌های میانبر را به این صورت تعریف می‌کنیم (این کد‌ها را از این وبلاگ گرفتم):

(when (require 'multi-term nil t)
       (global-set-key (kbd "<C-up>") 'multi-term-next)
       (global-set-key (kbd "<C-down>") 'multi-term-prev)
       (setq multi-term-buffer-name "term"
         multi-term-program "/bin/zsh"))

; Define keybindings
(when (require 'term nil t) ; only if term can be loaded..
       (setq term-bind-key-alist
           (list (cons "C-c C-c" 'term-interrupt-subjob)
               (cons "M-u" 'previous-line)
               (cons "M-e" 'next-line)
               (cons "M-o" 'term-send-forward-word)
               (cons "M-n" 'term-send-backward-word)
               (cons "C-c C-j" 'term-line-mode)
               (cons "C-c C-k" 'term-char-mode)
               (cons "M-r" 'term-send-backward-kill-word)
               (cons "M-w" 'term-send-forward-kill-word)
               (cons "<C-left>" 'term-send-backward-word)
               (cons "<C-right>" 'term-send-forward-word)
               (cons "C-r" 'term-send-reverse-search-history)
               (cons "M-x" 'term-send-raw-meta)
               (cons "M-m" 'term-send-raw-meta)
               (cons "M-c" 'term-send-raw))))

با آن تابع اول دو کلید سراسری را زمانی که یک multi-term ایجاد شد تعیین می‌کنیم. این کلید‌ها امکان دسترسی سریع به ترمینال را وقتی در بافر دیگری هستیم فراهم می‌کنند. همین‌طور اسم بافر ترمینال و نوع پوستهٔ مورد استفاده را تعیین می‌کنیم که زیاد لازم نیست‌، ولی بودنش ضرری هم ندارد (خود multi-term با خواندن متغیر محیطی SHELL پوستهٔ مورد استفاده را کشف می‌کند).

باقی کلید‌های تعریف شده را هم کمی تغییر دادم تا با ergoemacs-mode روی چینش workman سازگار باشد. شما هم می‌توانید با توجه به نیاز‌تان تغییرشان دهید.

line-mode, char-mode

این دو مد multi-term هر کدام امکانات متفاوتی دارند. مثلا در line-mode با بافر ترمینال مثل یک بافر معمولی ایمکس برخورد می‌شود که به راحتی قابلیت ویرایش دارد. char-mode همان مد پیش‌فرض ترمینال است. در مرحلهٔ قبل برای این دو مد هم کلید میانبر تعریف کردیم.

هماهنگ‌سازی ایمکس و ترمینالش در OpenBox

ممکن است شما هم مثل علیرضای عزیز‌، از i3 استفاده نکنید. این است که لازم می‌شود به روش متفاوتی نسبت به میز‌کار‌تان این امکانات بحث شده در این پست را فراهم کنید. علیرضا زحمت کشیده و روشی برای راه‌اندازی همچین چیزی در OpenBox ساخته. این روش را در وبلاگش بخوانید.