![](https://habrastorage.org/webt/ma/sd/72/masd72zxdvoj7zd7ktb_xgrnvq8.png)
Недавно я столкнулся с серьезным препятствием, когда работал над возможностью перетаскивать вкладки в приложении Warp: если попытаться передвинуть конкретную вкладку, она потянет за собой всё окно. Понадобилась целая неделя изысканий и экспериментов, чтобы установить, откуда берет начало этот баг. Но в конечном итоге я исправил это в pull request-е, который состоял менее чем из десяти строк кода! Это несоответствие усилий выхлопу также заставило меня проникнуться сознанием того, что создание ПО включает в себя гораздо больше, чем просто написание кода. В этой статье я расскажу, как всё происходило.
Немного контекста: Warp – это терминал для разработчиков на базе Rust. При его создании мы использовали собственный кастомный UI-фреймворк, так что всё, что касается вкладок и перетаскивания, пришлось разрабатывать с нуля.
На первый взгляд проблема, которой я занимался, выглядела несложной: как дать пользователям возможность перемещать вкладки так, чтобы не двигалось всё окно? Такое поведение можно встретить в других приложениях – Chrome или Electron, если выставить нужные настройки, так что я знал, что сделать это реально. Однако каждый раз, когда я пытался дублировать это поведение в Warp или простеньких тестовых приложениях, окно с панелью заголовка упорно тащилось следом. Не имело значения ни то, что панель не содержала контента и была скрыта, ни то, что я что-то изобразил на этом участке.
![](https://habrastorage.org/webt/xn/f6/2i/xnf62i6nkfjzzwy-iuf1izwwzte.gif)
В попытках разобраться в проблеме я обратился сперва к документации Apple для API к macOS. К сожалению, на деталях эта документация особо не останавливается, и я не сумел отыскать какого-то одного четкого значения, которое нужно было бы задать, чтобы предотвратить перетаскивание. Там было очень подробно расписано, как инициировать перемещение через другие части окна, но не указывалось ничего для отключения перетаскивания панели. То немногое, что удалось найти, я опробовал на тестовом приложении, результата это не дало.
Далее я обратился к примерам, в которых, как я точно знал, удалось реализовать то, что мне нужно: Electron и Chrome. К счастью, и то и другое – проекты с открытым кодом, так что в них можно копаться сколько душа пожелает. К несчастью, оба проекта отличаются сложностью, поэтому нырнуть в код с нулевыми фоновыми знаниями и понять, как там устроены окна на macOS, оказалось непростым делом.
Начал я с Electron, потому что знал, что там есть настройка, которую можно выбрать, чтобы получить именно то поведение, которого я добивался. Я отслеживал эту настройку по коду, пока не понял, где она применяется, а затем попытался воссоздать те изменения в создаваемых окнах, которые она запускает. Я перепробовал всё, что хоть сколько-то отличалось от нашей реализации в Warp, но раз за разом приходил в тупик. В итоге у меня появилось гораздо более глубокое понимание того, как работает Electron, но идей, как усовершенствовать Warp, не прибавилось.
Далее я обратился к Chromium, что представляло значительно больше затруднений, так как на этот раз у меня не было никакой стартовой точки. Я провел многие часы за поиском по коду, стараясь выявить те фрагменты, которые вроде бы были связаны между собой. Как это было и с Electron, всякий раз, когда я находил что-то, что выглядело применимым, я радостно бросался опробовать это локально, и меня неизбежно встречало всё то же неумолимое перетаскивание.
Наконец, спустя несколько дней, которые не принесли никакого существенного прогресса в поиске решения, я наткнулся на комментарий в кодовой базе Chromium, где говорилось о возможной оптимизации перемещающего поведения. Этот комментарий приводил ссылку на запись в системе отслеживания ошибок. Сама запись не давала дополнительного контекста, но, в свою очередь, ссылалась на сообщение в коммите, и вот там-то меня и ждало озарение, в котором я нуждался:
![](https://habrastorage.org/webt/ji/zy/bo/jizybo8f_9uhhgv_q4yp2_oaisu.png)
[MacViews] Исправлено непреднамеренное перемещение окон, особенно при попытке переместить вкладки.
Есть два пути, которые приводят к перетаскиванию окна приложения на Mac по экрану:
— Медленный путь инициируется приложением: происходит проверка попадания при нажатии на кнопку мыши, и что-то (чаще всего представление фрейма) вызывает ‘[self.windowperformWindowDragWithEvent:theEvent]’, чтобы начать перетаскивание на стороне сервера.
— Быстрый путь предполагает, что окно пересылает на сервер карту с зонами, для которых доступно перетаскивание. При ‘mouseDown’ в этих зонах перетаскивание начинается, не дожидаясь приложения.
Комментарий к коммиту в подробностях описывает процесс (судя по моим поискам, нигде больше не задокументированный), с помощью которого macOS определяет, какие части окна можно передвигать, а какие нет. Что важно, он приводит еще и такую оговорку к поведению в целом:
Еще одна сложность: представление, которое располагается за панелью заголовка окна при полноразмерном представлении контента, даже если панель скрыта, создает пробел в карте перетаскивания, только если аннулирует ‘mouseDown’ и возвращает YES для `acceptsFirstResponder`.
Наконец-то у меня появилось разумное предложение, для которого была прописана ясная логика, и оно помогло мне отчетливее понять проблему, которая передо мной стояла. Я включил необходимые настройки в тестовое приложение, и оно тут же заработало именно так, как я и ожидал! В результате портирования этих изменений непосредственно в Warp образовался крохотный pull request с едва ли не самым высоким соотношением усилий к выхлопу в моей карьере:
![](https://habrastorage.org/webt/t-/ib/c-/t-ibc-f2hvyqyvnhg7cavady1sm.png)
То, что столь незначительные изменения потребовали такой колоссальной работы, подчеркнуло для меня, насколько важна в программировании глубина понимания, а не скорость написания кода. При устранении бага необходимо выстроить модель ошибочного поведения, чтобы избавиться от самой проблемы, а не ее симптомов.
Напоследок отмечу: тот факт, что критически важную информацию я в итоге нашел в комментарии к коммиту, где какой-то программист изложил своё понимание в помощь будущим разработчикам, ясно показывает, как много дает распространение знаний. Когда сидишь над багом днями, а то и неделями и наконец тебя настигает эврика – это приносит глубокое удовлетворение. Но еще приятнее вообще избавить от этой работы свою команду и тех, кто будет поддерживать проект в будущем! Также я очень благодарен и сообществу, работающему с открытым кодом, за то, что предоставляет примеры и документацию, на которых мы все можем учиться.
![](https://habrastorage.org/webt/vw/go/4z/vwgo4zmcgfcf90do05mpygq6z0k.gif)
Если вам интересно собственноручно опробовать перетаскивание или взглянуть на терминал для разработчиков на базе Rust – скачивайте Warp вот здесь.
Комментарии (41)
13werwolf13
06.04.2023 05:34+18скачивайте Warp вот здесь.
а можно было прям вот рядом с сылкой указать что оно доступно только для макоси? 90% переходов же будут зазря..
ris58h
06.04.2023 05:34+4И остальные тоже, т.к. чтоб им пользоваться надо создать аккаунт и залогиниться.
13werwolf13
06.04.2023 05:34+24создавать аккаунт.. логиниться.. чтобы использвоать терминал..
мне кажется разработчику надо завязывать с запрещёнными веществами.
Tim777
06.04.2023 05:34+1И все это надо преодолеть, чтоб получить аналог bash completion + man -k приделанные к xterm через сопли из какого нибудь electron.
Sau
06.04.2023 05:34+12Можно просидеть неделю и не исправить ни одной строки кода (а исправить единственное значение в базе данных, например).
Ivan22
06.04.2023 05:34+9данные это не хуже чем код, я тоже днями сижу за поиском ошибок в данных.
Из самого последнего например исходные данные число 12.342525632414
а после миллиона ETL имеем 12.34 и все. Казалось бы просто тип где-то промежуточный с 2-мя знаками после запятой, да?? Ан не-е-е-е-т. Все куда хитрее!!!
Где-то в середине эти данные проходили через Excel (гусары молчать!) который "сильно умный" и исходное число сохранил в science виде 1.234E+01 и так его в csv и сохранял.
В 99% случаев обычно дальше на этапе лоада csv в базу все заканчивается еррором "type mismatch" но не у нас, у нас умные девелоперы, они в стейджинг льют все в varchar, без ошибок, а при заливке в нормализованное DWH используют try_to_number( ) который прекрасно понимает числа в science виде и выдавал без всяких ошибок на выходе нормальнрый numeric(38,19) за одним исключением - число то было уже обрезанное до 12.34.
То же в общем расследование нетривиальное.
vitecd
06.04.2023 05:34+4господин пэжэ... люди мучаются, мучаются, и тут херакс... эксель, и это ещё не финал...
Usage
06.04.2023 05:34Полностью вас понимаю, у нас такая же проблема, у нас решили с помощью прослойки на проверки типов и округлении до нужного количества знаков.
m_OO_m
06.04.2023 05:34+3На самом деле, довольно стандартный результат работы со сложными системами. Сидишь день, голова пухнет, в результате изменишь одну строку.
Namynnuz
06.04.2023 05:34+15Шта?.. Вот уж действительно из 21-го века терминал...
deseven
06.04.2023 05:34+16Да, идея отличная, но необходимость логина чтобы пользоваться терминалом это какая-то лютая дичь. Убийца iTerm убил сам себя.
Bringoff
06.04.2023 05:34+1На самом деле для меня Warp реально оказался удобнее iTerm. Но я уже давненько им пользуюсь. Мне кажется, когда я первый раз его пробовал, регистрация еще не была обязательной. Если бы я решил попробовать его сейчас, это могло бы отпугнуть.
kalempir
06.04.2023 05:34+4Это когда я оцениваю задачу на 30 минут и сижу над решением неделю. Оплата почасовая. 30 минут оплачиваются, остальное время за счет благотворительности )
vitecd
06.04.2023 05:34-2начиная с "...мы использовали собственный UI фреймворк..." а на зачем? их же так мало, ни один не подходит???
VADemon
06.04.2023 05:34+2Я не знаю этого конкретного случая, но как пример - скорость. Вот сравнение терминалов Линукса. Было где-то еще одно, где сравнивался терминал с GPU-accelerated text rendering. Не знаю как сейчас в Винде, но разница при большом выводе в STDOUT (в терминал, cmd) и даже банальный файл - может при паре тысяч строк уже измеряться секундами.
Из других вселенных: почитайте про веселье с ListView на Android, хотя казалось бы... список да список. Ещё веселого, попробуйте отрендерить список на 10-50к элементов на... скажем таблицах и голом HTML против Win32. Все эти lazy loading
от прогрессане от хорошей жизни.vitecd
06.04.2023 05:34-1продолжим, а на зачем? у вас какого размера экран? зачем РЕНДЕРИТЬ список на 50К элементов?
VADemon
06.04.2023 05:34Чтобы не надо было самому писать lazy loading
Скроллить. Например историю чата. Сколько сообщений держит тот же Discord за раз?
Поиск и фильтрация без лишней логики (см. п. 1)
Напоследок, 50к - пример. Я для браузера создал страницу для оффлайн-просмотра на 10к элементов. Грузится уже не моментально но работает. Максимум Javascript-а - это встроенный кусок из интернета, который сортировку делает. Затраты времени минимальны, скроллинг без проблем, CTRL+F работает.
Я уверен, что старый Win API без проблем 50к условных переварит (в дебри не лез), в пример думаю о RegEdit. При этом "переварит" не значит "быстро отрендерит", рендеринг элементов и текста в XnView (вертикально 4K) медленный и рисуется буквально на глазах.
PS: Минусы не мои
PPS: Претензию про "отрендерит" понял. Речь, конечно, идет об обработке. Ибо у ListView уже на этом этапе проблемы. Другая программа (Windows, кастомный UI-toolkit) замедлялась при скролле вниз текста на много строк, т.е. скалирование было не от числа отрендеренных строк, а числа обработанных (т.е. размера текста). Да, в основном я говорю о размере списка и его обработке, рендеринг идет отдельно. Извиняюсь.
Mingun
06.04.2023 05:34То, что столь незначительные изменения потребовали такой колоссальной работы, подчеркнуло для меня, насколько важна в программировании глубина понимания, а не скорость написания кода.
В итоге плохо разработчик усвоил урок, на пересдачу. Главное не то, что он вернул YES, а зачем это сделал. Через полгода очередной джун удалит "бесполезный" кусок и опять будите неделю тратить на 10 строк? Где комментарии?
Mingun
06.04.2023 05:34+5Люди, что с вами не так? Неужели 4 человека, поставившие минус, считают нормальным, чтобы вместо того, чтобы описать в коде решение, почему так сделано, где он его нашел, можно воткнуть 10 строк кода "найденных где-то в инете" и забить? Вас ничему его опыт не научил? А если бы чувак в хромиуме не описал, что этот код делает, разработчик warp-а до сих пор бы искал решение своей проблемы? Что за эгоизм? Нашел решение нетривиальной задачи — будь добр, опиши, что оно делает, а не гордись тем, что оставил в наследство следующему разработчику 10-строчники со странным кодом без описания.
andi123
06.04.2023 05:34+3Да уж. Все самые критичные багфиксы в унаследованном ПО, на последнем месте работы это изменения 1 числа или строки.
Время от начала до исправления - недели!
Eugenlusz
06.04.2023 05:34А как вот эту проблему в чатгпт собираются решать? Ну вот не знает он что так нужно. Это значит надо какой-то макрос добавить - типа вставь вот сюда вот такой код и никогда его не трогай. Но тогда программисты потребуются, кто угодно писать приложения тоже конечно сможет, но недолго - до первой баги? Кхмммм.
Или две ветки - одна от чатгпт, вторая фиксы от девелоперов и перед релизом/тестированием сливать их? А программисты будут мужественно сидеть и фиксить конфликты в своей ветке?edo1h
06.04.2023 05:34Ну вот не знает он что так нужно.
Какой шаг в описанном решении задачи по вашему мнению он принципиально не может (и никогда не сможет) сделать?
Eugenlusz
06.04.2023 05:34+1ну наверно воткнуть две бесполезных функции только потому что где-то в хромиуме они есть. Вот если бы в каждой апке такие были, то он бы конечно воткнул, а так выходит что проблема мало кого интересует, соответственно никто и не парится с ее решением.
Bringoff
06.04.2023 05:34+6Ну, товарищ молодец, нашёл решение в хромиуме. Но мне было бы интереснее прочитать статью от того человека, который это как раз в хромиуме и пофиксил. Как он это нашёл, раз это нигде не задокументировано.
arTk_ev
06.04.2023 05:34+1По статистике git удаляю код в 2 раза больше чем добавляю новый, включая новые фичи.
Фиксы багов - это в основном удаление лишнего кода. Каждый лишний код, лишнее условие, лишняя сложность - это код бага.
kraidiky
06.04.2023 05:34Самый продуктивный день по соотношению результата к написанному в моей жизни был когда я весь день пялился в код и ловил его на брейкпоинтах во всем местах пока наконец не удалил в одном месте три буквы и всё заработало правильно.
senchik
06.04.2023 05:34-4В сухом остатке, коммитер гений, а автору в поле баранов пасти, а не код писать?)
DustCn
Если документация говно, а сторонний код преставляет собой черный ящик, то можно просидеть и больше.