Давайте рассмотрим несколько примеров.

Как выключить отображение главного меню? Документация Emacs по этому поводу пишет такое:

You can toggle the use of menu bars with M-x menu-bar-mode. With no argument, this command toggles Menu Bar mode, a global minor mode. With an argument, the command turns Menu Bar mode on if the argument is positive, off if the argument is not positive. To control the use of menu bars at startup, customize the variable menu-bar-mode.

Очень часто последнее предложение интерпретируют неправильно, например, так:

(setq menu-bar-mode nil)

Что ж, иногда это даже работает.

Что насчёт другого встроенного режима, scroll-bar-mode?

Снова обратимся к документации Emacs:

To control the use of vertical scroll bars at startup, customize the variable scroll-bar-mode (see Customization). Its value should be either right (put scroll bars on the right side of windows), left (put them on the left), or nil (disable vertical scroll bars).

Как думаете, какой правильный способ выключения отображения полос прокрутки?

Может быть, так?

(setq scroll-bar-mode nil)

Или так?

(setq-default scroll-bar-mode nil)

Я вас разочарую, но оба ответа неправильные. Пока я покажу ещё пару примеров, а чуть позже вернусь к scroll-bar-modeи покажу правильный ответ.

Как предлагается настраивать пакет markdown-mode? А всё так же, через setq:

;; Set custom markdown preview function
(setq markdown-live-preview-window-function #'my-markdown-preview-function)

;; always open the preview window at the right
(setq markdown-split-window-direction 'right)
;; always open the preview window at the bottom
(setq markdown-split-window-direction 'below)

;; delete exported HTML file after markdown-live-preview-export is called
(setq markdown-live-preview-delete-export 'delete-on-export)

Возможно, в случае markdown-mode это даже работает...

А сейчас следите за рукой. Во многих языках программированяи есть такая вещь как сеттеры (setter). Это функция, неявно вызываемая при присваивании переменной какого-либо значения. Как правило, сеттер не только меняет значение приватной переменной экземпляра класса, но и выполняет проверку корректности введённых данных и другие связанные с изменённым значением операции.

Разберём на самом простом примере. Пусть у нас есть такой объект Button:

let Button = {
  _enabled: true,
  _htmlElement: {},

  set enabled(value){
    // Сделать кнопку доступной
    this._enabled = value;
    if (value) {
      this._htmlElement.removeAttribute("disabled");
    }
    this._htmlElement.setAttribute("disabled", true);
  }

  get enabled(){
    return this._enabled;
  }
};

У этого объекта есть одно публичное свойство -- enabled. Когда мы присваиваем ему значение true, связанному с нашим объектом элементу в DOM присваивается свойство disabled. Когда значение равно false, указанное свойство удаляется.

Вопрос: что произойдёт, если мы присвоим значение напрямую полю _enabled?

Правильно, ничего.

Вернёмся к Emacs, точнее к Emacs Lisp. В нём нет понятия приватности. Всё что есть в модуле -- доступно везде. То есть мы можем прийти и с помощью setq изменить значение любой переменной модуля. Но результат будет примерно таким же, как в случае присваивания значения напрямую приватному полю экземпляра класса.

Разработчики Emacs придумали костыль, который назвали customize. Если обычная переменная в модуле объявляется с помощью defvar, то переменные, которые должны торчать наружу и которые пользователь имеет право изменять с помощью customize, должны быть объявлены с помощью defcustom.

Функция defcustom позволяет определить не только название переменной и значение по умолчанию, но также геттеры и сеттеры, и это нужно использовать.

Конечно, значение переменной, объявленной с помощью defcustom, всё так же можно изменить через setq, но сеттеры при этом не вызываются.

Обратимся к исходному коду Emacs, frame.c, и посмотрим объявление переменной menu-bar-mode, про которую я говорил выше:

  DEFVAR_LISP ("menu-bar-mode", Vmenu_bar_mode,
               doc: /* Non-nil if Menu-Bar mode is enabled.
See the command `menu-bar-mode' for a description of this minor mode.
Setting this variable directly does not take effect;
either customize it (see the info node `Easy Customization')
or call the function `menu-bar-mode'.  */);
  Vmenu_bar_mode = Qt;

Выделю самое главное:

See the command menu-bar-mode for a description of this minor mode. Setting this variable directly does not take effect; either customize it (see the info node Easy Customization) or call the function menu-bar-mode.

Недословный перевод:

См. команду menu-bar-modeдля описания этого дополнительного режима. Установка значения этой переменной напрямую не имеет эффекта; используйте customize (см. раздел Easy Customization) или вызывайте функцию menu-bar-mode.

Т. е. для того, чтобы гарантированно отключить главное меню, можно использовать как минимум 2 способа.

Способ первый:

(menu-bar-mode -1)

Способ второй, правильный:

(custom-set-variables '(menu-bar-mode nil "Выключить главное меню"))

Теперь ответ на вопрос, заданный в начале статьи:

(custom-set-variables '(scroll-bar-mode nil "Отключить полосы прокрутки"))

Можно объединить оба вызова в один:

(custom-set-variables
  '(scroll-bar-mode nil "Отключить полосы прокрутки")
  '(menu-bar-mode nil "Отключить главное меню"))

Вывод: не изменяйте значения переменных, объявленных в пакете с помощью defvar. Используйте custom-set-variables для изменения переменных, объявленных с помощью defcustom.

Что ж, с этим разобрались. Пора переходить к следующей дичи, которую я часто вижу в документации пакетов Emacs.

Авторы некоторых пакетов или просто люди из интернета иногда пишут в своём init.el примерно такой код:

(require 'package)
(push '("melpa" . "https://melpa.org/packages/") 'package-archives)
(push '("nongnu" . "https://elpa.nongnu.org/nongnu/") 'package-archives)

Выполните свой init.elнесколько раз с помощью eval-buffer, а потом попробуйте открыть список доступных для установки пакетов.

Правда, неприятно когда список состоит из сотен одинаковых записей? Это всё потому, что функция push не проверяет целевой список на наличие дубликатов. Сколько раз push вызовите, столько раз элемент и добавится:

(defvar fruits nil)
(push 'Apple 'fruits)  ;; (Apple)
(push 'Orange 'friuts) ;; (Orange, Apple)
(push 'Apple 'fruits)  ;; (Apple, Orange, Apple)
(push 'Melon 'fruits)  ;; (Melon, Apple, Orange, Apple)
(push 'Orange 'fruits) ;; (Orange, Melon, Apple, Orange, Apple)

Функция add-to-list более интеллектуальна:

(defvar fruits nil)
(add-to-list 'fruits 'Apple)  ;; (Apple) 
(add-to-list 'fruits 'Orange) ;; (Orange, Apple)
(add-to-list 'fruits 'Apple)  ;; (Orange, Apple)
(add-to-list 'fruits 'Melon)  ;; (Melon, Orange, Apple)
(add-to-list 'fruits 'Orange) ;; (Melon, Orange, Apple)

Вывод: используйте add-to-list вместо push.

Комментарии (3)


  1. vtb_k
    22.05.2024 09:27
    +2

    Еще бы как правильно шрифты настраивать инструкцию. Есть set-fontset-font, modify-all-frames-parameters и конечно же add-to-list 'default-frame-alist. Что из них более правильно использовать?


    1. SmokerSF
      22.05.2024 09:27

      Настройка шрифтов разбирается в книге автора. Статья с подробным разбором будет, возможно, позже.


    1. ilyagoz
      22.05.2024 09:27

      M-x customize-create-theme, конечно же.