Когда мы взаимодействуем с текстовым файлом при помощи редактора, то, что мы видим, не всегда отражает содержимое файла. Да, содержимое файла с неформатированным текстом — это байтовые коды, закодированные в таких форматах, как ASCII, UTF8 и UTF16, и в этих байтовых кодах находится источник истины. Но в конечном итоге, именно текстовый редактор выбирает, как интерпретировать и отображать пользователю источник истины (двоичные коды). Это значит, что два файла могут выглядеть одинаково или один и тот же файл может выглядеть по-разному в зависимости от редактора.

Текстовый редактор может подсвечивать (или нет) отдельные части на основании распознанного им синтаксиса, может управлять отображением табов (2 пробела, 4 пробела или даже 8). Он решает, как кодировать нажатие клавиши Tab, например, как \t или как заданное количество пробелов. То же относится и к нажатию на клавишу Enter для создания новой строки — будет ли она кодироваться как \n (UNIX) или \r\n (Windows), зависит от конфигурации редактора.

Текстовый редактор скрывает подробности, чтобы пользователю не пришлось слишком много думать. Однако довольно часто такие подробности протекают сквозь защитный слой, который пытается создать редактор. И мы часто не замечаем подобные тонкости, пока не столкнёмся с ними.

Основная цель моей статьи — поделиться своим опытом и проблемами, с которыми можно столкнуться, работая с неформатированным текстом.

Табы и пробелы

Пробелы существовали задолго до табов. Изначально табы были придуманы, чтобы снизить частоту использования клавиши пробела и Backspace.

Однако люди продолжают спорить о том, что же использовать в проектах, табы или пробелы. Вопрос не в том, нужно ли использовать саму клавишу Tab, а в том, должны ли текстовые редакторы вставлять пробелы или табы при её нажатии, то есть способ кодирования пробелов, которые мы видим в редакторе при нажатии на клавишу Tab.

Думаю, основное преимущество табов перед пробелами — это обеспечиваемая ими гибкость. При использовании табов можно совместно работать с разными людьми, предпочитающими видеть различные уровни отступов без необходимости распространять свои предпочтения на других. (К тому же символ таба занимает меньше места, но не думаю, что сегодня это так уж важно.)

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

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

Редактор 1, где табы занимают 4 пробела:

// 4 таба + 3 пробела
function calculate(a, b,
                   c, d) {
    // Какая-то логика
}

Редактор 2, где табы заниают 2 пробела:

// 4 таба + 3 пробела
function calculate(a, b,
           c, d) {
    // Какая-то логика
}

Это одна из причин, по которым я чаще всего использую пробелы. Если вам в конечном итоге приходится настраивать ширину табов в соответствии с предпочтениями других людей, то в чём вообще смысл их применения?

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

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

Мягкие и жёсткие переносы

При написании неформатированного текста мы рано или поздно доходим до точки, когда он становится слишком длинным. Во многих текстовых редакторах (Notepad, Notepad++, Neovim и даже VSCode) по умолчанию текст продолжает нарастать по горизонтали, пока пользователь не нажмёт Enter, чтобы создать новый разрыв строки. Это может быть достаточно неудобно по сравнению с большинством клиентов электронной почты или мессенджеров, где текст автоматически переносится, что упрощает его чтение.

Ниже я покажу различия текстов без переносов и с переносами.


Текст без переносов:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent fermentum felis nec elit bibendum, sit amet tempus sapien volutpat. Sed eu congue massa, non condimentum diam. Aenean vel consectetur odio. Suspendisse nec diam ac nisl bibendum tempor. Ut rutrum maximus velit, commodo consectetur nulla auctor ac. Curabitur neque dui, scelerisque in facilisis at, sagittis quis massa. Ut non tempor arcu. Vivamus elit massa, pulvinar vitae tellus ut, lobortis sagittis elit. Aenean vehicula varius eros, vitae pellentesque lorem consequat non. Aenean gravida velit id pellentesque tempor. Aenean ut purus nulla. Curabitur fringilla felis consequat ante condimentum porta. Curabitur id ex in libero rhoncus lobortis sit amet ac ligula.

Текст с переносами:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent fermentum
felis nec elit bibendum, sit amet tempus sapien volutpat. Sed eu congue massa,
non condimentum diam. Aenean vel consectetur odio. Suspendisse nec diam ac nisl
bibendum tempor. Ut rutrum maximus velit, commodo consectetur nulla auctor ac.
Curabitur neque dui, scelerisque in facilisis at, sagittis quis massa. Ut non
tempor arcu. Vivamus elit massa, pulvinar vitae tellus ut, lobortis sagittis
elit. Aenean vehicula varius eros, vitae pellentesque lorem consequat non.
Aenean gravida velit id pellentesque tempor. Aenean ut purus nulla. Curabitur
fringilla felis consequat ante condimentum porta. Curabitur id ex in libero
rhoncus lobortis sit amet ac ligula.

Как видите, текст с переносами намного проще читать. Именно поэтому многие люди вставляют переносы, достигнув определённого количества символов (обычно примерно 78). На самом деле, в используемом мной текстовом редакторе Neovim для выполнения этой процедуры переноса на текущей строке достаточно нажать gq.

Эта процедура вставки символа новой строки между текстом, чтобы он переносился, называется жёсткими переносами. Я тоже долгое время пользовался ею, но поскольку вы разбиваете строку на несколько строку просто для удобства чтения в редакторе, у вас возникают следующие проблемы:

  • Постоянное выполнение жёсткого переноса вручную утомляет, даже если есть горячие клавиши.

  • Если вы скопируете текст с переносами и вставите его в другое приложение, например, в мессенджер, то он будет выглядеть некрасиво, особенно на смартфонах. Это происходит потому, что при уменьшении размера экрана мессенджер сам выполняет перенос каждой строки, если она не помещается на экране по вертикали. Чтобы лучше разобраться в этом явлении, рекомендую прочитать статью The problems with hard wrapping email body text.

  • После того, как вы начнёте добавлять символы новой строки в текст, чтобы сделать его более читаемым, вы жертвуете грепабельностью. «Грепабельность» — это степень удобства поиска по тексту при помощи различных инструментов наподобие grep. В случае поиска по тексту с жёсткими переносами, в котором предложение разбито на несколько строк, инструменты поиска могут потерпеть неудачу, ведь обычно они выполняют поиск построчно.

    Принцип грепабельности очень важен и экономит много времени, особенно при работе с кодовой базой. Например, в общем случае не очень оптимально разбивать сообщения об ошибках и сообщения логов на несколько строк, ведь так мы теряем грепабельность. Есть вы хотите узнать подробности, то см. статью Greppability is an underrated code metric [перевод на Хабре].

Каким же будет решение? Перестать ставить жёсткие переносы и просто использовать мягкие; во многих современных текстовых редакторах уже есть решения для мягких переносов. Именно их я и начал использовать в последнее время. Я уверен, что можно включить эту возможность во многих современных редакторах. В Neovim для этого достаточно выполнить следующие команды:

:set linebreak
:set wrap
:set columns=80

Новая строка

Допустим, вы совместно работаете с другими людьми через Git, изменили одну строку кода и запушили её в ветвь. Внезапно коллеги начали жаловаться на то, что diff вашего коммита выглядит так, как будто вы изменили весь файл.

Если вы не знаете, в чём причина, то вам понадобится некоторое время, чтобы понять, что это ваш текстовый редактор автоматически преобразовал все символы разрыва строки в свой формат.

Некоторые мои друзья используют в качестве основного текстового редактора VSCode, и у них возникала эта проблема. Поначалу они были очень сбиты с толку. К счастью, им на помощь пришёл я.

Я совершу краткий экскурс в историю, потерпите немного.

Давным-давно, когда ещё не у всех компьютеров были мониторы, люди взаимодействовали с ними при помощи замечательных устройств под названием «телепринтер». Эти устройства состояли из клавиатуры для ввода и принтера для вывода. Оператор вводил текст, а затем отправлял команду компьютеру и получал результаты, которые выглядели вот так:

В контексте телепринтера символ новой строки (newline) означал физическое перемещение каретки печатной машинки в начало строки (CR: Carriage Return, «возврат каретки») с последующим поднятием бумаги вверх на одну строку (LF: Line Feed, «подача на одну строку»).

Эти первые машины были предназначены для работы с телетайпами, а CR и LF были необходимы перед печатью новой строки. И это повлияло на ранние версии Unix (1971 год) и MS-DOS (1981 год). В те времена телетайпы по-прежнему использовались, поэтому в той или иной степени они повлияли на обе операционные системы. Разработчики Unix решили упростить процесс создания новой строки, считая символ LF достаточным для обозначения конца строки, а MS-DOS сохранил полное сочетание CR + LF. Думаю, ребят из Microsoft больше беспокоило легаси и аппаратная совместимость.

И все эти стандарты продолжают существовать и поныне. В файлах, создаваемых в Windows для новых строк, используются \r\n, а в генерируемых в системах наподобие UNIX (Linux/MacOS/BSD) используется \n. Однако многие современные текстовые редакторы способны читать и парсить оба формата обеих операционных систем, поэтому мы не замечаем таких небольших различий. Если только они не влияют на наши diff в git.

Чтобы избежать подобных ситуаций, можно правильно настроить текстовый редактор, использовать линтер и создать файл .git/config с нужным значением autocrlf.

Заключение

Даже неформатированный текст иногда может быть не так прост, как кажется. Понимание его внутреннего устройства может вам помочь, особенно если вы часто с ним работаете.

Дополнение

По счастливому совпадению Breck Yunits, которого я недавно для себя открыл, сделал обзор моей статьи на YouTube. В видео он говорит о том, что клавиша Tab используется и как разделитель табличных данных в файлах с неформатированным текстом, а также делится собственным опытом работы с табами/пробелами и переносами.

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


  1. mrCOTOHA
    30.10.2024 09:31

    Оффтоп:

    для выполнения этой процедуры переноса на текущей строке достаточно обычного... нажать gq

    15 лет пользуюсь Linux-ом, но Vi (и его потомки) остаются для меня за гранью моих возможностей. Самый быстрый и удобный редактор, говорят, если знаешь кучу неочевидных хоткеев и команд, которые больше нигде не применяются.

    По теме:

    \r\n и \n эт прям песня. Особенно на волне перехода с недружественной ОС на православные системы. Случались диалоги, типа "ты мне скрипт на баше прислал, а он не работает, <диалог с выяснением обстоятельств>, я пару значений поменял ... мне в винде Notepad-ом удобнее было".

    Как любитель табуляции - грустил, когда вкатывался в python. Но, блин, да. Пробелы отображаются одинаково в разных редакторах, в отличие от табов.


  1. nronnie
    30.10.2024 09:31

    Мне казалось, что Git с настройками по умолчанию все сам делает. При push переводит все в \n, а при pull или clone - в соответствии с целевой OS. Разве не так?


  1. cssfish
    30.10.2024 09:31

    Изначально табы были придуманы, чтобы снизить частоту использования клавиши пробела и Backspace.

    Ну это "половина правды", на печатных машинках табы были нужны для облегчения ввода таблиц. Но главное в том, что табы могли быть разной ширины, т.к. колонки в таблицах бывают разные. Настроил перед вводом докумета эти "плашечки" вверху, и с каждым "нажатием таба" перескакиваем на ввод нужной колонки. То есть это не "заменитель ввода N пробелов за раз", а способ "прыгнуть на следующую позицию".

    Ровно в таком же виде эта идея перекочевала в визуальные текстовые редакторы типа word`а и там отлично работает (метки на линейках по вертикали и горизонтали). А вот в plain text редакторах проблема в том, что табом пытаются и несколько пробелов покрыть, и тот самый "прыг" обеспечить (таб может быть разной длины если рядом с ним символы добавлять), что при перемешивании табов и пробелов дает кашу.

    На деле всем скорее нужна некая третья сущность (заменитель N пробелов), которую и пытаются эмулировать редакторы, вставляя и удаляя N пробелов за раз. Такое мое имхо..