понедельник, 29 декабря 2008 г.

SXEmacs - behind the scene

Как и обещал приоткрою немного занавес SXEmacs и расскажу, а точнее покажу, как устроена модель разработки SXEmacs. Но сначала про разработчиков:

hroptatyr (aka Sebastian Freundt)
Немец, живёт в Берлине, математик, занимается разработкой систем компьютерной алгебры, зарабатывает на жизнь игрой на бирже, любит мощные компьютеры и большие мониторы, носит очки
lg (aka Zajcev Evgeny)
Гимнаст, работал охранником 6 лет, хорошо владеет рукопашным боем, неплохо обращается с холодным оружием и отлично стреляет. Живёт в лесистой части г. Ульяновск, любит работать в туалете, выпивать, читать книжки и детей
njsf (aka Nelson Ferreira)
Живёт в Америке, любит фотографировать и путешествовать, есть красивая подружка, имеет хорошее чувство юмора.
PeanutHorst
Не совсем разработчик, а скорее противовес в команде SXEmacs, пессимистичен, работает в OpenBox под FreeBSD, часто несёт глупости, тестирует SXEmacs на различных платформах.
JackaLX (aka Steve Youngs)
Создал SXEmacs, домохозяин, имеет кучку детей, держит магазин, торгующий шмотками и вещами с символикой SXEmacs, ждёт когда кто-нибудь переведёт первый доллар в качестве денежного пожертвования на развитие SXEmacs, есть красавица жена Мишель с большой грудью, которая зарабатывает на жизнь семье

Это список активных разработчиков на текущий момент, так что он не совсем полный, но для понимания поднаготщины — хватит.

Итак, скачайте эту картинку и откройте её в просмоторщике в полном формате, т.е. без использования fill-to-width или схожих функций.

Те кому интересны все детали можете скачать SVG исходник

ReST source Скачать оригинал

вторник, 23 декабря 2008 г.

Условия гонки в ASYNEQ

Давайте для начала я покажу вам скриншот моего SXEmacs:

Скриншот

Ничего необычного не замечаете? У меня почти невидимые клеточки. Цвет #C4C4C4 на фоне #CCCCCC да ещё с моим дальтонизмом — я их действительно еле вижу, главное что я знаю, что они там есть и если приглядеться, то я их отчётливо вижу. Особо внимательные заметят, что в каждую клеточку помещается 4 символа по горизонтали и 2 по вертикали.

Теперь немного утомительной истории. Путём наблюдений и экспериментов я пришёл к выводу, что у меня есть некая особенность — проблемы лучше решаются, если во время размышлений вести записи на листочке в клеточку. Первый раз я это заметил решая просто гигантское количество задач по планиметрии в 8 классе. В дальнейшем доходило даже до маразма; для того, чтобы решить одну задачу на вступительных экзаменах в МГУ мне пришлось разлиновать в клеточку выданный листочек. Я всё не мог понять с чем это связано, и вот, когда начал использовать XEmacs в году '97-98, то понял. Как сейчас помню, сидел набирал текст какой-то программы и возникло острое ощущение, что буквы, которые я так старательно набираю, сейчас разбегутся, а вместе с этими буквами разбегутся и вкладываемые мысли. Тогда то я и понял, что хорошие мысли порождаются свободным разумом, но истинно свободный разум также легко теряет мысли как и порождает их, для разума-рабочей лошадки, нужна «клетка». Вот такая вот психология.

Со временем, поэкспериментировав с цветом и размером клеток в окнах (S)XEmacs я нашёл оптимальный (дающий наибольшую продуктивность) для себя вариант — тот, что на картинке.

И вот представьте, сижу я как обычно печатаю текст, пытаясь вложить в него хоть немного смысла, как вдруг, буквы, в прямом смысле этого слова, начинают убегать и смешиваться друг с другом. Всё думаю, доигрался, колёсики уехали. Как ни странно, но первая мысль была именно, что проблемы у меня, а не у SXEmacs. Немного успокоившись задался вопросом почему так произошло, ведь раньше такого не было. Нашёл особенность — использовался flyspell режим, который обычно выключен. Покопавшись в flyspell нашёл особенность, что self-insert команда может породить вызов accept-process-output. Дальнейшее разбирательство привело к ASYNEQ. А текст так и не написал :(

ReST source Скачать оригинал

UPD: Код для сетки

(defface lg-grido-face
  '((((class color) (background dark))
     (:foreground "gray10"))
    (((class color) (background light))
     (:foreground "gray77"))
    (t (:foreground "gray77")))
  "Face for grid.")

(push '("grido" (face-foreground 'lg-grido-face)) xpm-color-symbols)

(defconst lg-square-64x64-xpm
  (concat
   "/* XPM */\n"
   "static char *mini_square_xpm[] = {\n"
   "/* columns rows colors chars-per-pixel */\n"
   "\"64 64 2 1\",\n"
   "\"       c None s background\",\n"
   "\".      c gray77 s grido\",\n"
   "/* pixels */"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"................................................................\"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n,"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "\"    .                                                           \"\n"
   "};"))

(set-face-background-pixmap
 'default
 (make-image-specifier (vector 'xpm :data lg-square-64x64-xpm))) 

понедельник, 22 декабря 2008 г.

SXEmacs - лучший программный продукт года

Решил немного помечтать и вообразить, что нужно SXEmacs, чтобы выиграть приз «Лучший программный продукт года». Было ли вообще такое, чтобы GNU Emacs выигрывал этот приз? Что же необходимо изменить и сделать в SXEmacs, чтобы это событие произошло?

  • Кардинально улучшить подсистему обработки событий (event loop).
  • Реализовать подсистему отображения (redisplay) используя современные средства, например Cairo. Использовать современные библиотеки виджетов, например GTK+.
  • Реализовать нормальную мультиязыковую поддержку.
  • Поймать все утечки памяти при использовании BDWGC и сделать его сборщиком мусора по умолчанию.
  • Реализовать чистую интеграцию различных ЯП, таких как Python, Ruby и др.
  • Улучшить документацию как для разработчиков так и для пользователей.
  • Создать более продвинутый и быстрый сопоставитель текста с образцом на замену регулярных выражений. На базе него сделать, чтобы font-lock работал корректно и быстро.
  • Создать систему совместной разработки на базе SXEmacs.

info

Призываю читателей к дискуссии. Опишите свой взгляд, расскажите каким вы хотели бы видеть ([S]X)Emacs.

Подробное изложение каждой части этого плана я буду публиковать отдельными постами (не обязательно в правильном порядке). Потом их можно будет склеить в одну статейку и оформить как некий путь развития SXEmacs. Сегодня будет небольшой обзор подсистемы событий.

Подсистема событий

Самое удивительное, что сейчас вообще нет этой подсистемы ;). Есть определённые части, выполняющие необходимые действия, но отсутствует целостность, расширяемость и сила. В существующей ПС есть только высокий уровень доступа, нет разделения API на низкий/высокий уровни, поэтому если требуется что-то добавить, то необходимо делать много низкоуровневой мишуры, с помощью копи&паст программирования — утомительно.

В SXEmacs было решено использовать libev как низкоуровневую базу для ПС. Выбор стоял между liboop, libevent, libev и одной проприетарной библиотекой, которую планировалось перелицензировать (уже было получено согласие правообладателя). liboop отпала первой — библиотека практически мертва. libevent является почти de-facto стандартом для создания софта с ПС, но у нескольких разработчиков SXEmacs был негативный (как и позитивный тоже) опыт использования libevent и как раз ограничения, которые существуют в ней плюс просто ошеломляющие результаты тестирования скорости libev в сравнении с libevent не позволили выбрать libevent. Проприетарная библиотека отпала так как она зависела от дополнительной большой библиотеки, а так же в ней не очень удобный API (хотя, видимо, очень мощный) и отсутствовала документация.

Так зачем вообще всё это нужно? А для того, чтобы полностью избавиться от внутренних блокировок в SXEmacs. Блокировки бывают двух типов:

  1. Выполнение elisp кода приостанавливается в ожидании какого-то события.
  2. Блокирует сам вызов библиотечной функции.

К пункту 2 относятся вызовы для разрешения DNS имён, такие как gethostbyname(3) и getaddrinfo(3), вызов connect(2), а также, как это не удивительно, вызов write(2). В GNU Emacs реализован неблокирующий вызов connect если передано ключевое слово :nowait1 в процедуру make-network-process, при этом вызов make-network-process всё же может заблокировать, если заблокировало разыменование имени хоста. В SXEmacs вызов connect всегда блокирует, поэтому open-network-stream всегда блокирует. Что касается write(2), то по настоящему всё же write не блокирует, ибо дескриптор был переведён в неблокирующий режим для неблокирующего чтения, но все Емаксы, получая EWOULDBLOCK от write(2), делают select в ожидании, когда разрешат записать.

К пункту 1 относятся любые вызовы accept-process-output и call-process. Что произойдёт если accept-process-output ожидает данных и в этот момент приходит какое-нибудь событие? Если событие системное, то оно обрабатывается немедленно, если событие специальное или командное2, то оно будет отложено и записано в очередь событий, которые будут обработаны позже.

Асинхронный обработчик событий

В SXEmacs есть такая вещь называется ASYNEQ — это возможность асинхронной обработки событий. Не всякое событие может быть обработано асинхронно. В общем случае асинхронная обработка командных событий ведёт к условиям гонки (race condition)3. Специальные же события без проблем можно обрабатывать асинхронно. Именно по этой причине xlib и xwem работают более предсказуемо под SXEmacs, ибо они порождают просто тонны специальных событий, которые в XEmacs обрабатываются синхронно как и командные.

Часто командные события всё же можно обрабатывать асинхронно. Условия гонки были обнаружены токмо спустя год после того как в SXEmacs появился ASYNEQ. Это говорит о том, что условия гонки крайне редки и сильно зависят от специфики elisp кода, то есть автоматически невозможно определить возникнет ли в конкретном случае race condition или нет. Возможно, необходимо определить переменную asyneq-ignore-command-events, которую можно связать со значением t в особых случаях, когда необходимо запретить асинхронную обработку командных событий.

Как организовать новую ПС

Что мы хотим от новой ПС:

  • Целостность — одно место обработки событий, отсутствие костылей на вроде как при потенциальном блокировании write в process-send-string
  • Простой и мощный API как с низкоуровневыми, так и с высокоуровневыми конструкциями
  • Возможность интеграции других подсистем событий в ПС SXEmacs. Для начала X11 и Glib
Схема ПС

Сх. 1. пример работы ПС SXEmacs (event-loop.svg)

Использовать новую ПС будут:

  • На низком уровне
    • Асинхронное разыменование DNS
    • Остальные потенциально блокирующие вызовы
    • Клей для других ПС
  • На высоком уровне
    • Event API на elisp уровне
    • Таймеры
    • Прогнозируемые предикаты
    • И т.д.

Попробую изобразить в виде схемы, что будет происходить при возникновении командного события, которое порождает вызов open-network-stream. Как видим, событие помещается в очередь событий и когда до него дойдёт очередь, оно будет обработано, что породит вызов процедуры open-network-stream, которой, в свою очередь, необходимо разыменовать имя хоста и подсоединиться к нему, т.е. произвести две потенциально блокирующих операции. За время выполнения open-network-stream мы два раза посетим обработчик событий и в случае, если нам действительно нужно будет ждать, мы сможем обработать ещё кучу событий из очереди.

Именно так я и вижу работу новой ПС в SXEmacs.


1Вроде RMS был ярым противником ключевых слов
2Командные события порождаются действиями пользователя (нажал кнопку, мышкой побаловался и т.д.)
3Не буду описывать подробности, но, поверьте мне, я думал что помешался когда наблюдал эти условия гонки при работе с flyspell. Впрочем, психологическую составляющую этого опыта можно вынести отдельным постом, будет интересно

ReST source Скачать оригинал

Расширение REPL

Возникла тут идея реализовать возможность расширения возможностей REPL в SXEmacs, а особенно его Read части. Но сначала про Print часть.

Пользовательская печать

В XEmacs уже давно были предпосылки к созданию пользовательской печати elisp-объектов. Так, например, при задании структуры с помощью defstruct можно было указать :print-function — процедуру, которая осуществляет печать объекта данной структуры. Уже давно существовала магическая переменная custom-print-functions со следующим напутствием:

This variable is not used at present, but it is defined in hopes that a future Emacs interpreter will be able to use it.1

—custom-print-functions (cl.el)

В SXEmacs это напутствие было выполнено и появилась возможность пользовательской печати. Это моментально решило проблему печати сложно-рекурсивных структур, которая до этого решалась с помощью изменения переменных print-level и print-length.

Уже существуют пакеты, которые используют возможность пользовательской печати. Использовать эту возможность крайне легко:

(defstruct (test-prs
            (:print-function
             (lambda (tp s pl)
               (princ (format "#<test-prs %s>"
                              (test-prs-name tp)) s))))
  type              ; type of struct: `frs', `snd', ..
  (name "default")  ; name of object
  state             ; state of object
  plist             ; user defined properties list
  )
(make-test-prs :name "Done")
==> #<test-prs Done> 

Пользовательское чтение

Раз у нас есть пользовательская печать, то почему бы нам не иметь пользовательского чтения. В SXEmacs была реализована возможность пользовательского чтения под кодовым названием ureaders (от user readers). В новой встроенной (built-in) переменной ureaders хранится список пользовательских читалок. Пользовательская читалка это пара, состоящая из имени и функции-читалки от одного аргумента, которая возвращает elisp-объект или порождает ошибку. Приведу пример. Допустим, мы хотим чтобы следующая конструкция работала:

(setq mm '#<test ao -- 1
                 test -- 2
                 val -- "test">)
==> ((val . "test") (test . 2) (ao . 1)) 

реализуем:

(defun my-test-reader (input)
  (flet ((string-trim (s)
           (replace-in-string s "\\(^[ \t]+\\|[ \t]+$\\)" "")))
    (let ((ss (mapcar 'string-trim (split-string input "\n")))
          (rv nil))
      (dolist (s ss)
        (let ((rw (mapcar 'string-trim (split-string s "--"))))
          (push (cons (intern (car rw)) (read (cadr rw)))
                rv)))
      rv)))

(push '("test" . my-test-reader) ureaders) 

На первый взгляд эта возможность кажется не совсем нужной, ведь у нас и так есть мощнейшее средство — макросы. Это так, конечно, но в связке с пользовательской печатью почти прямой доступ к кишкам чтения выглядит интересно. Позже я продемонстрирую захватывающие вещи, которые можно реализовать с помощью ureaders.


1Эта переменная пока не используется и была создана в надежде, что в будущем интерпретатор Emacs сможет её использовать.

ReST source Скачать оригинал

пятница, 19 декабря 2008 г.

Просмотр страничных файлов в Wand-mode

Недавно в Wand-mode была добавлена возможность просмотра страничных файлов, таких как PDF, EPS, MPEG и т.д., у которых есть несколько страниц. Навигация по страницам до ужаса проста:

  • PgDown - Следующая страница
  • PgUp - Предыдущая страница
  • Home - Первая страница
  • End - Последняя страница
  • g или M-g - Переход на выбранную страницу. Передать номер страницы можно посредством универсального аргумента или ввести интерактивно.

Конечно, Wand-mode это вам не xpdf или gv, но для быстренького ознакомления с документом или быстренького выковыривания нужной картинки из книжки или видео вполне сгодится.

Текущая страница и общее количество страниц будет отображено на экране если у вас non-nil Wand-mode-show-fileinfo.

Заметим, что операции над картинкой будут осуществляться только для текущей страницы, а не для всех страниц файла.

ReST source Скачать оригинал

Wand-mode - алгоритм устранения красных глаз

Внимание!

Для корректного отображения этой страницы вашему браузеру необходима поддержка формата MathML.

Как и обещал напишу про то, как улучшить алгоритм устранения красных глаз у Wand-mode и как его тестировать. Возможно, что найдутся желающие его поковырять и улучшить ибо я не эстет да ещё и дальтоник к тому же, так что доволен тем, что есть и сейчас.

Описание алгоритма

Вкратце, проходим по всем пикселям выделенной области и смотрим на их красноту. Если пиксел достаточно красный, то заменяем его на тёмный. Затем немного размываем пикселы лежащие внутри эллипса, вписанного в выделенную область. Определение красности пиксела и его замена на более тёмный происходит в функции Wand-fix-red-pixels. Аргумент PIXELS, который она принимает — это список троек, где каждая тройка имеет вид (RED GREEN BLUE). RED, GREEN и BLUE принимают значения от 0 до 255.

После того как пикселы подправлены краснота уйдёт, но кое-где могут остаться резкие перепады цвета. Для того чтобы их сгладить мы накладываем эллиптическую маску (ибо зрак, в основном, круглой формы ;)) и применяем размытие по Гауссу с радиусом, который нам выдаст функция Wand-mode-redeye-blur-radius.

С чего начать

Неплохим началом в ковырянии алгоритма может стать функция Wand-mode-redeye-blur-radius. Это должна быть такая хитрая функция, которая даёт небольшие значения для маленьких входных и очень медленно растёт. Сейчас это убогая WH16 , где W — ширина выделенной области, а H — высота.

Тестирование улучшенного алгоритма

Для начала скачайте вот эту картинку с примерами красных глаз. Затем для тестирования вам понадобится следующая команда:

(defun Wand-test-redeye (arg)
  "Apply redeye reduction algorithm to ARG's region."
  (interactive "p")
  (let ((pp '(((41 41 349 302) (39 34 86 307)
               (44 42 354 58) (47 42 94 151))      ; first region
              ((29 31 315 676) (36 33 108 660)
               (86 79 168 468))                    ; second region
              ((28 26 335 885) (26 25 147 930)
               (21 18 267 774) (21 18 129 792))    ; third region
              ((26 26 343 1219) (28 29 87 1161)
               (25 18 294 1044) (23 18 127 1036))  ; fourth region
              ((17 15 431 1468) (18 17 376 1463)
               (17 20 310 1468) (17 20 232 1477)
               (13 15 116 1458) (13 15 25 1466)
               (23 25 253 1336) (21 25 132 1378))  ; fifth region
              )))
    (mapc (lambda (reg)
            (Wand-operation-apply 'redeye-remove image-wand reg))
          (nth (1- arg) pp))
    (Wand-redisplay))) 

Откройте картинку redeye-samples.jpg с помощью M-x Wand-display RET. Тестировать алгоритм устранения красных глаз можно с помощью команды C-u <NUM> M-x Wand-test-redeye RET, где <NUM> это номер области над которой нужно произвести тестирование. Как видно из кода всего есть 5 областей. После выполнения команды проверьте результаты с помощью программы для увеличения, я предпочитаю Lupe — быстро работает и вообще отличный софт. Если для всех областей результаты схожи с тем что справа, то у вас неплохо получилось.

Присылайте результаты в список рассылки sxemacs-devel@, для лучшего варианта предусмотрен приз ;)

ReST source Скачать оригинал

среда, 10 декабря 2008 г.

Логотип SXEmacs

Удивительно, но у SXEmacs нет логотипа в формате векторной графики, а тот, что есть в растре, по части качества просто отвратительный.

Я попробовал тут свои силы в создании лого в Inkscape. Вот, что из этого вышло:

Лого

и логотип для бета версии

Бета Лого

Вы можете также скачать исходный sxemacs.svg

Надеюсь Стиву понравится и оный станет официальным логотипом SXEmacs.

ReST source Скачать оригинал