Русский
Русский
English
Статистика
Реклама

Ide

Начинаем писать под stm8, выбираем среды разработки и стартуем

28.04.2021 12:07:29 | Автор: admin
image

На пути в программировании stm8 есть развилка, о ней сегодня и поговорим.

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

Первый установка ST Visual Develop и выбор в качестве компилятора COSMIC Бывший платный, а ныне бесплатный, но со своими заморочками; регистрация, получение ключа, и прочие танцы с бубном.

Второй же вариант, более простой VS Code + PlatformIO и компилятор SDCC полностью свободный. И опять же не все так просто. Sdcc не умеет исключать не используемые функции. Я решил этот вопрос хоть и успешно, но не без дополнительных действий при написании кода.

Первая среда, для любителей всё делать правильно


Для начала нам нужен ST Visual Develop. Устанавливаем и ставим запуск ярлыка всегда от администратора. В придачу к нему нам дают ST Visual Programmer, полезный инструмент, особенно когда стоит защита от записи и надо разблокировать микроконтроллер, а ведь китайские blue pill всегда приходят заблокированными. Китайцы бояться что мы украдём их круто оптимизированный Blink.

Вот так выглядит STVD
image
Я так понял её создали, когда в моде были 16 битные цвета...

Дальше нужно будет получить компилятор COSMIC и его лицензионный ключ. Заполняем то что просят, получаем дистрибутив и делаем запрос ключа по электронной почте (тут рулетка кому то сразу придёт, кому то придётся подождать).

После пройденного лабиринта из форм и запросов, когда всё уже установлено, начнём
image
image
При первом запуске нужно указать расположение ключа, его лучше поместить в директорию компилятора. После создания нажимаем F7, ошибок быть не должно. Если писать на чистых регистрах, то всё готово, но я такой хардкор не люблю, поэтому продолжим и добавим SPL библиотеку.

Распакуем куда-нибудь и заходим в папку STM8S_StdPeriph_Lib\Project\STM8S_StdPeriph_Template. Тут у нас шаблон проекта stm8s_conf.h это конфигурационный файл библиотеки, через него выбирается контроллер. Зайдём в main тут сразу с первых строк #include "stm8s.h" это ссылка на основную библиотеку, а так же кусок кода отладки который начинается с #ifdef USE_FULL_ASSERT, без отладочного кода будут сыпаться ошибки.

Теперь когда мы прошлись по верхам давайте пробовать запускать библиотеку. Добавляем в проект из шаблона main и конфигурационный файл в. В include files добавляем всё из STM8S_StdPeriph_Lib\Libraries\STM8S_StdPeriph_Driver\inc.
image
Теперь всё должно собраться.

Добавим stm8s_gpio.c и соберём простецкую мигалку. У меня один из светодиодов висит на D3, конфигурация ноги на выход выглядит так:

GPIO_DeInit(GPIOD);GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_OUT_PP_LOW_SLOW);

Её вписываем в main до бесконечного цикла.

А вот функция смены состояния. GPIO_WriteReverse(GPIOD, GPIO_PIN_3); вписываем её в бесконечный цикл.

Но вот незадача, в SPL нету функций задержки, скопипастим из примера GPIO в библиотеке. Там она выглядит следующим образом.

void Delay(uint16_t nCount){  /* Decrement nCount value */  while (nCount != 0)  {    nCount--;  }}

Впишем её в конец перед #ifdef USE_FULL_ASSERT. Так же впишем её прототип в начало, где под это выделено место в шаблонном main.

/* Private function prototypes -----------------------------------------------*/void Delay (uint16_t nCount);

Ну и наконец впишем функцию со значением в бесконечный цикл после функции смены состояния: Delay(0xFFFF);

Подключаем ST-Link и прошиваем, для этого нажимаем Start Debugging и Run. Светодиод моргает значит всё хорошо.

У кого не получилось вот полный main.c
/**  ******************************************************************************  * @file    Project/main.c   * @author  MCD Application Team  * @version V2.3.0  * @date    16-June-2017  * @brief   Main program body   ******************************************************************************  * @attention  *  * <h2><center> COPYRIGHT 2014 STMicroelectronics</center></h2>  *  * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");  * You may not use this file except in compliance with the License.  * You may obtain a copy of the License at:  *  *        http://www.st.com/software_license_agreement_liberty_v2  *  * Unless required by applicable law or agreed to in writing, software   * distributed under the License is distributed on an "AS IS" BASIS,   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  *  ******************************************************************************  */ /* Includes ------------------------------------------------------------------*/#include "stm8s.h"/* Private defines -----------------------------------------------------------*//* Private function prototypes -----------------------------------------------*/void Delay (uint16_t nCount);/* Private functions ---------------------------------------------------------*/void main(void){GPIO_DeInit(GPIOD);GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_OUT_PP_LOW_SLOW);  /* Infinite loop */  while (1)  {GPIO_WriteReverse(GPIOD, GPIO_PIN_3);    Delay(0xFFFF);  }  }void Delay(uint16_t nCount){  /* Decrement nCount value */  while (nCount != 0)  {    nCount--;  }}#ifdef USE_FULL_ASSERT/**  * @brief  Reports the name of the source file and the source line number  *   where the assert_param error has occurred.  * @param file: pointer to the source file name  * @param line: assert_param error line source number  * @retval : None  */void assert_failed(u8* file, u32 line){   /* User can add his own implementation to report the file name and line number,     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */  /* Infinite loop */  while (1)  {  }}#endif/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/


Теперь посмотрим со стороны на эту среду.

По плюсам; у неё есть полноценная отладка, поддержка SPL из коробки. По минусам; она старая из этого вытекает, не очень приятный интерфейс и старый диалект си, на который многие свежие библиотеки придётся переписывать, в частности ставить переменные только вначале функций. Вес кода оставляет желать лучшего. Ну последний камень, этот инструмент может просто взять и вылететь, такое бывает, нечасто, но всё же.

Вторая среда, для тех кто не любит заморачиваться.


Второй подход это свободный и обновляемый компилятор SDCC, а так же среда PlatformIO.

Для начала установим VS Code и станем рабами Microsoft, далее найдём расширение PlatformIO.

image

Ждём пока миллион ползунков пройдёт до конца и перезапускаем программу. Открываем расширение (может быть и само откроется). Создаём новый проект и выбираем ближайшую плату с stm8s (микроконтроллер можно будет изменить в конфигурационном файле). В качестве фреймворка выбираем SPL мы же не ардуинщики, нас интересует хардкор.

Не удивляйтесь, проект будет создаваться прилично долго, потому как он на ходу будет подгружать компилятор, библиотеки и тд. И вот перед нами возник девственно голый проект, закрываем его он нам больше не нужен. В меню где мы создавали проект, открываем пример spl-blink, суть в том что blink генерирует неплохой шаблон в котором можно сразу писать. Правим пример под себя и прошиваем, всё моргает.

Разберем поподробнее среду разработки. Во первых весь проект должен лежать в src, иначе среда ведёт себя неадекватно. Во вторых открываем stm8s_conf.h и видим что все библиотеки кроме GPIO закомментированы, если этого не сделать то у мк не хватит памяти что бы поместить весь SPL в микроконтроллер (помните в начале я говорил что он загружает все функции что видит в код?).

Выходит что тут мы должны будем ручкам включать и отключать библиотеки, а если пойти дальше в оптимизацию как я и сделал, то нужно будет каждую функцию включать и отключать. Часть конфигурационного файла у меня выглядит следующим образом:

/* Private define ------------------------------------------------------------*/#define _GPIO_DeInit#define _GPIO_Init#define _GPIO_WriteReverse#define _CLK_DeInit#define _CLK_SYSCLKConfig#define _TIM4_DeInit#define _TIM4_ITConfig#define _TIM4_ClearITPendingBit#define _TIM4_Cmd#define _TIM4_TimeBaseInit#define _TIM4_ClearFlag#define _HAL_GPIO_WritePin#define _GPIO_WriteLow#define _GPIO_WriteHigh//#define STM8S003/* Includes ------------------------------------------------------------------*/#include "stm8s.h"/* Uncomment the line below to enable peripheral header file inclusion */#if defined(STM8S105) || defined(STM8S005) || defined(STM8S103) || defined(STM8S003) ||\    defined(STM8S001) || defined(STM8S903) || defined (STM8AF626x) || defined (STM8AF622x)// #include "stm8s_adc1.h" #endif /* (STM8S105) ||(STM8S103) || (STM8S001) || (STM8S903) || (STM8AF626x) */#if defined(STM8S208) || defined(STM8S207) || defined(STM8S007) || defined (STM8AF52Ax) ||\    defined (STM8AF62Ax)// #include "stm8s_adc2.h"


Каждую функцию я включаю через макрос, для этого у меня переписана большая часть SPL. Если вам оптимизация сильно не нужна, то такой упоротостью можете не заниматься.

Пройдёмся по преимуществам: работает почти из коробки, полностью бесплатно без попрошайничества, редакции языка обновляются и не придётся учить и переписывать код под си 80-90г. Сам интерфейс настраиваемый и намного приятнее. Для тех кто не любит win есть linux версия.

По минусам: нету полноценной отладки (над ней активно работают, но пока ничего приближённого к cosmic я не видел), в проекте небольшой хаос, раскидать по папкам пока нельзя.

Послевкусие.


Ну и на последок есть ещё среды, варианты и компиляторы, но либо это тот же SDCC вкрученный силой в Eclipse или ещё куда и работающий хуже чем в VS Code, либо это платные варианты IAR, Raisonance. Я лично пользуюсь и тем и тем, но чаще VS Code. Рекомендовать ничего не буду каждому своё, увидимся в комментариях)

Подробнее..

Минимизация кликов и горячие клавиши для жизни разработчика Темнее Тёмной Темноты

02.07.2020 08:05:44 | Автор: admin
Хороший разработчик/аналитик/просто пользователь ПК стремится к оптимизации любого процесса. Будь то хоть включение чайника на кухне, пока снимаешь куртку зимой, а также к улучшению и модернизации рабочего места или ПО.
Медленный компьютер, тормозящие приложения, узкое использование инструментов с огромнейшими возможностями всё это демотивирует.
Попробуем расширить кругозор и оптимизировать каждый клик.



В статье разобраны 5 IDE, 2 приложения для работы с БД, 2 ОС, 2 браузера и 2 SSH программы и хранитель паролей.



Навигация
PhpStorm 2020.1.2
Notepad++ v7.8.7
Apache Netbeans 12
Sublime Text 3
Visual Studio Code 1.46.1
Redmine
Atlassian (Trello, Bitbucket, SourceTree 3.3.9, Jira, Confluence)
Windows 10
Linux
Google Chrome 83.0
Mozilla (Firefox 78.0b9, Thunderbird 68.9.0)
PL SQL Developer 13
DBeaver 7.1.0
Keepass 2.45
WinSCP 5.17.6
Putty 0.73
Прокачиваем мобильник

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

Вот основные подпункты:
  • Описание. Основное, что делает программа либо выжимка с Википедии
  • Горячие клавиши Совокупность клавиш, при одновременном нажатии которых происходит запрограммированное действие
  • Главное меню Оптимизация рабочего пространства
  • Выравнивание/Табуляция Автоформатирование отступов
  • Шаблоны кода Заранее написанные блоки кода, вызываемые по аббревиатурам и запрограммированной клавише, обычно Tab
  • Ссылки Ссылки на официальный сайт, сайт откуда можно скачать, описание в Википедии, мобильная версия если такая есть, а также просто полезные ссылки, допустим на статьи о приложении


ТТТ
Отдельно хочу выделить подпункт, который я везде называю ТТТ Темнее Тёмной Темноты.
Окрашивание в тёмный цвет всего, что можно + полезные ссылки, в основном на тёмные
темы с userstyles.org (почему-то прямая ссылка даёт иногда 504 ошибку, а ссылки на темы работают нормально), предварительно поставив плагин Stylus в Chrome или в Mozilla
Общие примеры:
Глобальные темы для браузеров:
darkreader
global-dark
ВК
Google Script
скрин



IDE


PhpStorm 2020.1.2




ТТТ
File Settings Editor Color Scheme Material Darker

Выравнивание/Табуляция
  • Code Reformat Code или CTRL+ALT+L
  • File Settings Editor Code Style


Горячие клавиши

Главное меню
File Settings Menus and Toolbars

Шаблоны кода
File Settings Editor Live Templates

Тестирование REST запросов внутри программы
Очень удобно если важна не визуальная составляющая ответа, внутрянка.
Tools HTTP Client Test RESTful Web Service


БД
View Tool Windows Database



SSH
Tools Deployment Browse Remote Host

GIT
  • Если установлен гит, то правой кнопкой мыши в любом файле GIT
  • VCS Git
  • В нижней панели Version Control


Экспорт настроек


Командная строка
Снизу вкладка Terminal

Тайм-трекинг
File Settings Tools Tasks Servers




Notepad++ v7.8.7



Плагины
Плагины Управление плагинами. Есть полезные:
  • XML Tools
  • QuickText (Это шаблоны кода)
  • Snippets
  • Customize Toolbar (Это настройка главного меню)
  • Compare (Diff)


Выравнивание/Табуляция
Опции Настройки Синтаксисы

Горячие клавиши
Опции Сочетание клавиш

ТТТ
Опции Определение стилей




Apache Netbeans 12




Горячие клавиши
Tools Options Keymap

Выравнивание/Табуляция
Tools Options Editor Formatting

Шаблоны кода
Tools Options Editor Code Templates (бонусом выставление курсора)

Главное меню
Tools Options Appearance Document Tabs, а также в вкладке Window

Командная строка
Window IDE Tools Terminal

ТТТ






Sublime Text 3




ТТТ
  • Preferences Color Scheme Monokai
  • Preferences Theme


Горячие клавиши
Preferences Key Bindings

Шаблоны кода
Tools Snippets




Visual Studio Code 1.46.1




Горячие клавиши
File Preferences Keyboard Shortcuts

Консоль
Terminal New terminal

Расширешия
View Extensions

Репозиторий
View SCM


Ссылки



Аналитика


Redmine


новый




ТТТ
При создании нового проекта есть выбор светлой или тёмной темы.


старый


ТТТ
Не забываем подправлять URL если он у нас домашний



Шаблоны
Скачиваем и устанавливаем Redmine.
Создаём, что надо, проекты и т.д.
Допустим нам надо заполнить по шаблону поля при создании новой задачи.
Для этого нам опять помогут UserScript`ы.
Устанавливаем TamperMonkey по аналогии с статьёй habr.com/ru/post/504664 (пункт Юзерскрипты в браузере), вставляем
код
// ==UserScript==// @name         redmineTemplate// @namespace    http://127.0.0.1/redmine*// @version      0.1// @author       You// @match        http://127.0.0.1/redmine*// ==/UserScript==var d = document.createElement('span');document.querySelector('#issue_tracker_id').parentNode.appendChild(d);d.style['color'] = 'red';d.style.width = '100px';d.style.cursor = 'pointer';d.style.paddingLeft = '30px';d.textContent = 'Шаблон'; d.addEventListener('click', function () {   //Трекер   $('#issue_tracker_id :contains(\'Поддержка\')').attr('selected', 'selected');   // Описание   $('#issue_description').val('Полное описание. \nПример:...');   // Срок завершения   $('#issue_due_date').attr('value',$('#issue_start_date').attr('value'));   // Готовность   $('#issue_done_ratio :contains(\'30 %\')').attr('selected', 'selected'); });



Появляется кнопка, жмём, автозаполняются поля теми значениями, которые мы указали.


Скрипт простой, но времени экономит вагон.
Можно в любой части редмайна вставить подобные скрипты и автоматизировать часто используемые части.




Atlassian


У Atlassian есть хаб в хабре, некоторые статьи будут из него

Confluence








Trello




Горячие клавиши

Шаблоны



Bitbucket







SourceTree 3.3.9




ТТТ
Инструменты Настройки Общее Theme

Шаблоны кода
Инструменты Настройки Пользовательские действия

Горячие клавиши
Подсвечены в главном меню у каждого пункта



Jira




Шаблоны кода
Через TamperMonkey по аналогии с старым редмайном (выше)
document.querySelector('#summary').value = 'Новая тема'







ОС


Windows 10




bat
Очень много батников нашлось в интернете и много статей в Хабре. Кому интересно, просто заходим в Хабр, вводим в поиск bat (либо сразу по ссылке) и изучаем возможности.


ТТТ


Пуск Параметры Персонализация Цвета Тёмный.
Как изменить цвет выделения в Windows 10
Как изменить цвет окон Windows 10

Ускорение


Оптимизация действий
  • Автозагрузка нынче перенеслась из WIN+R msconfig в Диспетчер задач (CTRL+SHIFT+ESC либо переходим в Пуск Параметры Приложения Автозагрузка).
  • Скрипт настройки Windows 10


Отключаем ненужные приложения
Если мы хотим, чтоб при запуске Windows сразу открывались нужные нам приложения, добавляем их в папку автозагрузки.
Обычно она по адресу C:\Users\ВАШ_ПОЛЬЗОВАТЕЛЬ\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup, либо можно так: WIN+R Вводим shell:startup, в папку вставляем приложения (лучше ярлыки)
Добавить приложение для автоматического запуска при начальной загрузке Windows 10
Отключить подтверждение перед установкой приложений (UAC контроль учётных записей). Жмём лупу справа от Пуска, вводим UAC, жмём Изменение параметров контроля учётных записей, бегунок вниз.

Внешний вид
Приводим рабочий стол в порядок, удаляем лишнее, переносим ярлыки, чтоб все были под рукой. Тоже самое делаем и с папками, сколько бы временных затрат это ни стоило. Упорядоченные папки, без шуток, экономят массу времени (но я до сих пор не могу разобрать злосчастную папку На потом).
Если вам мало места или вы ведёте двойную/тройную жизнь, допустим дизайнер и БДшник, используйте несколько рабочих столов



Linux




Да простят меня все, но я не Линуксоид. Собрал, что знал, думаю хоть что-то, да будет полезным




Браузеры


Google Chrome 83.0




Для ускорения обмена настройками между разными компьютерами, а также для быстрого подключения к сервисам Google заводим аккаунт и настраиваем синхронизацию. Не стесняемся объединять закладки в папки для экономии места.




Mozilla


Firefox Developer 78.0b9


Также, как и в Chrome для ускорения обмена настройками между разными компьютерами заводим аккаунт и настраиваем синхронизацию. И также объединяем закладки в папки для экономии места.
ТТТ


Ссылки



Thunderbird 68.9.0


Папки и фильтры
Если нам приходится много работать с почтой, системное разделение на папки по темам очень ускоряет работу и не отвлекает на ненужную почту.
Инструменты Фильтры сообщений. Настройка антиспама и пересылки раскладывания сообщений по папкам настраивается тут.

ТТТ
Инструменты Дополнения Темы Dark




Database


PL SQL Developer 13




Шаблоны кода
Они чуть другие, чем обычные шорткаты. Вставляются либо двойным кликом либо перетаскиванием в рабочее окно.
Вид Список шаблонов





DBeaver 7.1.0




ТТТ
  • Окна Настройки Общие Внешний вид Dark
  • Окна Настройки Общие Внешний вид Цвета и шрифты


Горячие клавиши
Окна Настройки Общие Клавиши

Выравнивание/Табуляция
  • Окна Настройки Общие Текстовые редакторы
  • Окна Настройки DBeaver Редакторы Редактор SQL Форматирование


Шаблоны кода
Окна Настройки DBeaver Редакторы Редактор SQL Templates

Диаграммы связей
Собственно из-за чего я и оставил DBeaver. Жмём на таблицу с CTRL, выбираем вкладку Диаграмма и видим все соединения с выбранной таблицей.




Храним пароли


Keepass 2.45




Храним пароли в одном месте.



FTP + SSH


WinSCP 5.17.6




Чтоб меняться подключениями между рабочими местами, пользуемся выгрузкой Инструменты Экспорт настроек
ТТТ
Внешний вид Интерфейс Theme Dark

Горячие клавиши
В главном меню Команды





Putty 0.73




Обмен подключениями между рабочими местами нашёл пока только такой способ через реестр Computer\HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions
ТТТ
Window Colours




Прокачиваем мобильник


Уделяем внимание папкам и объединению приложений. Лишний клик забывается, как только привыкаешь и запоминаешь, где что.
Если у вас уже настроена почта по папкам, то с мобильного телефона удобней смотреть уже сортированное. То есть настраиваем на компьютере, пользуемся на компьютере и на мобильном.
Боты в телеграм. Скептически к ним относился, пока пару штук не сделал и не понял всех возможностей. Склеивать их с различными Google-сервисами можно на ура, главное придумать, как оптимизировать время, создав или найдя уже существующего полезного бота.
Календарь. Тут всё просто. Используем его везде.
Чеклист допустим TickTick
Список дел, допустим Простой список дел или Задачи: Список задач. Нужны, чтоб не держать всё в голове и если не нравится календарь.

Итог


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

Полезные ссылки, которые также могут пригодится


Tabsbook
www.tabsbook.ru Пока для меня самый удобный менеджер закладок, работающий как в Chrome, так и в Mozilla. Если есть инструмент, объединяющий закладки кроссбраузерно, буду признателен поделившимся.


Adminer
www.adminer.org очень хорошая альтернатива www.phpmyadmin.net, умещается в один файл php.


Heroku
www.heroku.com облачная PaaS-платформа

Miro
Miro (до 2019 года RealtimeBoard) платформа для совместной работы распределенных команд (в том числе при дистанционной работе отдельных сотрудников), разработанная в России и вышедшая на международный рынок.
Официальный сайт
Википедия
Андроид

Airtable
Airtable представляет собой гибрид базы данных и электронной таблицы.
Официальный сайт
Википедия
Андроид

AWD - Android Web Developer
AWD PHP/HTML/CSS/JS IDE Android Web Developer (AWD) это IDE (интегрированная среда разработки) для веб разработчиков. Поддерживаются следующие языки и форматы: PHP, CSS, JS, HTML, JSON
Подробнее..

Обновление для IDE EmBitz 1.11

04.09.2020 22:23:02 | Автор: admin
На официальном форуме появилось обновление для IDE EmBitz 1.11. Оно включает в себя расширенный список поддерживаемых микроконтроллеров STM32, обновленные версии библиотек CMSIS, SPL, HAL и LL, а также текущую версию компилятора gcc-arm-none-eabi-9-2020-q2-update и GDB сервер EBlink, необходимый для прошивки и отладки микроконтроллеров.

Список поддерживаемых микроконтроллеров STM32 значительно увеличен. Их число превышает тысячу и я ограничусь перечислением семейств МК. Это STM32F0, STM32F1, STM32F2, STM32F3, STM32F4, STM32F7, STM32H7, STM32L0, STM32L1, STM32L4, STM32L4+, STM32L5, STM32G0, STM32G4 и STM32WB.
На данный момент это все существующие модели STM32 исключая семейство STM32MP1 (гибрид Cortex-A7 и Cortex-M4).

В этом обновлении интерфейс мастера создания проекта немного изменился по сравнению в оригинальным. Вкратце рассмотрим основные шаги.
Проект создается из меню File -> New -> Project.
Обновление добавляет тип проекта STM32. Выбираем его и приступаем к созданию проекта нажав на кнопку Go.
Спойлер


Следующим шагом требуется ввести имя проекта и выбрать место расположения на диске.
Спойлер


В следующем окне предлагается выбрать компилятор и настроить цели сборки. Оставляем значения по умолчанию.
Спойлер


После предлагается выбрать семейство МК. Для теста выберем STM32F4.
Спойлер


Далее следует выбрать серию. У меня в наличии отладочная плата с STM32F407ZET6 и поэтому выбрал STM32F407_417.
Спойлер


В следующем окне нам предлагается выбрать в списке модель микроконтроллера. Как вы наверное уже догадались я выбрал STM32F407ZE.
Спойлер


Кратко о других настройках.
Опция Add DebugRAM target добавит в проект отладочную цель сборки с выполнением кода из ОЗУ. Это не только уменьшает износ Flash-памяти, но и ускоряет запуск отладки за счет отсутствия затрат времени на стирание Flash-памяти и запись в нее. Но в микроконтроллере должно быть достаточно ОЗУ для кода и данных.
В списке Debugger output выбирается метод вывода отладочных сообщений функцией printf или ей подобной. Возможно использование Semihosting или EB monitor.
В списках ниже задается тип проекта (C или C++) и выбирается периферийная библиотека. Возможные варианты SPL, HAL и LL. Также можно вовсе не использовать периферийную библиотеку и в проект будет добавлена только CMSIS.
Нажатие кнопки Finish запустит создание проекта. По завершению на экране появится окно настройки отладчика.
Спойлер


Обычно настройки изменять не требуется и закрываем окна кнопкой OK.
Если было разрешено создание цели DebugRAM, аналогичное окно появится и для нее.
Спойлер


На этом создание проекта завершено.
Для проверки обновления напишем какую-то прогу. По традиции это будет мигание светодиодом.
Открываем файл main.c.
Спойлер


В проект включена библиотека SPL. Используем ее для инициализации порта и изменения логического уровня на нем для мигания светодиода.
Получился код (классика, ничего необычного).
#include "main.h"int main(void){    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);    GPIO_InitTypeDef g = {.GPIO_Pin = GPIO_Pin_10,                          .GPIO_Mode = GPIO_Mode_OUT,                          .GPIO_OType = GPIO_OType_PP,                          .GPIO_PuPd = GPIO_PuPd_NOPULL,                          .GPIO_Speed = GPIO_Speed_50MHz};    GPIO_Init(GPIOF, &g);    while(1)    {        GPIOF->ODR ^= GPIO_ODR_ODR_10;        for (volatile uint32_t i=0; i<2000000; i++);    }}

Заливаем и смотрим как мигает светодиод подключенный к PF10 (на китайской отладочной плате с STM32F407ZET6 он подключен к этому выводу).
Проект разместил на файлообменнике dropmefiles.com/EhWzz

На случай недоступности форума EmBitz продублирую ссылки здесь.
Обновление библиотеки www.dropbox.com/s/upmp6r1fuqltrzp/UpdateLib_1.0_For_EmBitz_1.11.zip
GDB сервер EBlink www.dropbox.com/s/fefp18w932vxii7/EBlink_2.8_For_EmBitz_1.11.zip
Для установки распакуйте архивы в папку с EmBitz.

Итог.
Обновление годное. Протестировано мной на десятке микроконтроллеров разных семейств и серий. Багов не обнаружено.

И в завершении мое личное мнение о IDE EmBitz.
В отличие от среды Eclipse и ее производных (к ним относятся большинство бесплатных IDE для STM32) неспешно работающих и требующих для комфортной работы достаточно мощный ПК, IDE EmBitz основана на среде Code::Block и менее требовательна к ПК.
Подробнее..

Настройка Xdebug3 для Laravel-приложения в Docker

30.01.2021 18:12:04 | Автор: admin

Начнём пожалуй, со структуры, в которой всё будет:

docker/ docker-compose.yml .env .env.example .gitignore services     database      dump      .gitignore     nginx      site.conf     php         Dockerfile         php.ini

А теперь обо всём по порядку:

Файл .gitingore содержит только одну строчку /.env

Файл .env.example в начале проекта такой же, как и .env

В файле docker-compose.yml содержится информация про все наши сервисы:

version: "3.7"services:  php:    build:      args:        uname: ${PHP_UNAME}        uid: ${PHP_UID}        gid: ${PHP_GID}      context: ./services/php    container_name: ${PROJECT_NAME}_php    image: ${PROJECT_NAME}_php    restart: unless-stopped    working_dir: /var/www/    volumes:      - ./services/php/php.ini:/usr/local/etc/php/php.ini      - ../:/var/www    environment:      COMPOSER_MEMORY_LIMIT: 2G      XDEBUG_CONFIG: client_host=${XDEBUG_REMOTE_HOST} client_port=${XDEBUG_STORM_PORT} remote_enable=1      PHP_IDE_CONFIG: serverName=${XDEBUG_STORM_SERVER_NAME}    networks:      - main_network    depends_on:      - db  db:    image: mysql:5.6    restart: unless-stopped    container_name: ${PROJECT_NAME}_db    command: --default-authentication-plugin=mysql_native_password    environment:      MYSQL_DATABASE: ${DB_DATABASE}      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}      MYSQL_PASSWORD: ${DB_PASSWORD}      MYSQL_USER: ${DB_USERNAME}    ports:      - ${DB_LOCAL_PORT}:3306    volumes:      - ./services/database/dump:/var/lib/mysql    networks:      - main_network  nginx:    image: nginx:1.17-alpine    restart: unless-stopped    container_name: ${PROJECT_NAME}_nginx    ports:      - ${NGINX_LOCAL_PORT}:80    volumes:      - ../:/var/www      - ./services/nginx:/etc/nginx/conf.d    networks:      - main_network    depends_on:      - phpnetworks:  main_network:    driver: bridge    name: ${PROJECT_NAME}_main_network    ipam:      driver: default      config:        - subnet: ${SUBNET_IP}/${SUBNET_MASK}

Из важного здесь стоит отметить ./services/php/php.ini:/usr/local/etc/php/php.ini - наш локальный файл php.ini (там некоторые конфиги дебаггера) будет намаплен на тот что внутри контейнера. XDEBUG_CONFIG - будет задана переменная окружения внутри контейнера php, которую потом будет испльзовать xdebug вместо значений по-умолчанию. Здесь мы задаем client_host - хост, к которому xdebug будет пытаться подключиться при инициации отладочного соединения. Этот адрес должен быть адресом машины, на которой ваш PhpStorm прослушивает входящие отладочные соединения. Получается так, что наша локальная машина находится в одной подсети с запущеными контейнерами, а её адресс будет первым в этой подсети. Таким образом, мы всегда можем знать каким будет адресс нашей машины, и позже зададим это значение в переменную XDEBUG_REMOTE_HOST . В CLIENT_PORT нужно будет задать порт, установленный на прослушивание в IDE (9003). XDEBUG_STORM_SERVER_NAME - имя сервера, который мы создадим в IDE позже. Этот параметр нужен, чтобы сообщить PhpStorm, как сопоставлять пути при подключении с докера (ведь у вас же открыты локальные файлы в редакторе, а код работает на удалённых; хотя при испльзовании volumes это не совсем так).

Вот, как выглядит файл окружения .env :

PROJECT_NAME=my_projectDB_DATABASE=my_project_dbDB_USERNAME=my_projectDB_PASSWORD=p@$$w0rdDB_ROOT_PASSWORD=toorPHP_UNAME=devPHP_UID=1000PHP_GID=1000DB_LOCAL_PORT=3377NGINX_LOCAL_PORT=8077XDEBUG_STORM_SERVER_NAME=DockerXDEBUG_REMOTE_HOST=192.168.227.1XDEBUG_STORM_PORT=9003SUBNET_IP=192.168.227.0SUBNET_MASK=28

На счёт подсети для проекта, то здесь мы задали 192.168.227.0 с маской 28, то-есть для всех устройств остаётся 32 - 28 = 4 бита, что равносильно 2 ** 4 - 1 = 15 контейнеров. Не 16 потому что в подсеть входит также наша локальная машина, которая, кстати, будет иметь адресс 192.168.227.1. Именно это значение мы задали в переменную XDEBUG_REMOTE_HOST.

Настройки веб-сервера site.conf:

server {    listen 80;    server_name 127.0.0.1 localhost;    client_max_body_size 5m;    error_log  /var/log/nginx/error.log;    access_log /var/log/nginx/access.log;    root /var/www/public;    index index.php;    location / {        try_files $uri $uri/ /index.php?$query_string;        gzip_static on;    }    location ~ \.php$ {        try_files $uri =404;        fastcgi_split_path_info ^(.+\.php)(/.+)$;        fastcgi_pass php:9000;        fastcgi_index index.php;        include fastcgi_params;        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;        fastcgi_param PATH_INFO $fastcgi_path_info;    }}

В нашем Dockerfile (тот что для php) нужно не забыть установить и включить xdebug. Для этого добавляем 2 строчки:

RUN pecl install xdebugRUN docker-php-ext-enable xdebug

Стоит отметить, что будет испльзована последняя (то-есть 3) версия (есть различия в конфигурации по сравнению с 2).

Полный Dockerfile:

FROM php:7.4-fpm# Arguments defined in docker-compose.ymlARG unameARG gidARG uid# Install system dependenciesRUN apt-get update \    && apt-get install -y \        git \        curl \        dpkg-dev \        libpng-dev \        libjpeg-dev \        libonig-dev \        libxml2-dev \        libpq-dev \        libzip-dev \        zip \        unzip \        cronRUN pecl install xdebugRUN docker-php-ext-enable xdebugRUN docker-php-ext-configure gd \  --enable-gd \  --with-jpegADD ./php.ini /usr/local/etc/php/php.ini# Clear cacheRUN apt-get clean && rm -rf /var/lib/apt/lists/*# Install PHP extensionsRUN docker-php-ext-install pdo pdo_mysql pdo_pgsql pgsql mbstring exif pcntl bcmath gd sockets zip# Get latest ComposerCOPY --from=composer:latest /usr/bin/composer /usr/bin/composer# Create system user to run Composer and Artisan CommandsRUN groupadd --gid $gid $unameRUN useradd -G www-data,root -s /bin/bash --uid $uid --gid $gid $unameRUN mkdir -p /home/$uname/.composer && \    chown -R $uname:$uname /home/$uname# Set working directoryWORKDIR /var/wwwUSER $uname# Expose port 9000 and start php-fpm serverEXPOSE 9000CMD ["php-fpm"]

Также, зададим некоторые параметры исплнения в php.ini :

max_execution_time=1000max_input_time=1000xdebug.mode=debugxdebug.log="/var/www/xdebug.log"xdebug.remote_enable=1

Запустим наше приложение:

docker-compose up -d

И дальше пойдём в PhpStorm для настройки:

Создадим сервер с названием Docker и cделаем маппинг локального корня проекта (/var/www/quizzy.loc) на путь, по которому он лежит в докере (/var/www):

Дальше, нам нужно будет настроить использование интерпретатора php из докера:

Настраиваем php interpreterНастраиваем php interpreterВыбираем сервис, в котором находится php и указываем путь к интерпретаторуВыбираем сервис, в котором находится php и указываем путь к интерпретаторуЗадаём параметры запуска (через exec)Задаём параметры запуска (через exec)

Теперь можем перейти в подпункт "Debug" и настроить порт на котором запускать:

Настраиваем дебаггерНастраиваем дебаггер

Проверим, работает ли Xdebug:

Теперь можем поставить брейкпоинт в нашем коде и начать прослушивание входящих подключений:

Переходим в наш любимый браузер Firefox, устанавливаем и включаем плагин на нужной странице:

Перезагружаем страницу, и в PhpStrom должен поймать подключение:

Подробнее..

Java Core для самых маленьких. Часть 1. Подготовка и первая программа

11.02.2021 22:07:58 | Автор: admin

Вступление. Краткая история и особенности языка.

Как-то давно мы с моим товарищем и коллегой Егором готовили обучающий курс по Java Core. Но как-то не срослось и это дело не было доведено до какого-либо логического конца. И вот, спустя время, я решил, что не стоит пропадать добру и по-этому запускаю серию статей про Java Core для самых маленьких.

Начало разработки языка было положено еще в 1991 году компанией Sun Microsystems, Inc. Вначале язык был назван Oak (Дуб), но в 1995 он был переименован в Java. Публично заявили о создании языка в 1995 году. Причиной создания была потребность в независящем от платформы и архитектуры процессора языке, который можно было бы использовать для написания программ для бытовой электротехники. Но поскольку в таких устройствах применялись различные процессоры, то использование популярных на то время языков С/С++ и прочих было затруднено, поскольку написанные на них программы должны компилироваться отдельно для конкретной платформы.

Особенностью Java, которая решила эту проблему, стало то, что компилятор Java выдает не машинный исполняемый код, а байт-код - оптимизированный набор инструкций, которые выполняются в так называемой виртуальной машин Java (JVM - Java Virtual Machine). А на соответствующую платформу предварительно устанавливается JVM с необходимой реализацией, способная правильно интерпретировать один и тот же байт-код. У такого подхода есть и слабые стороны, такие программы выполняются медленнее, чем если бы они были скомпилированы в исполняемый код.

Установка программного обеспечения - JDK

В первую очередь, нам нужно установить на компьютер так называемую JDK (Java Development Kit) - это установочный комплект разработчика, который содержит в себе компилятор для этого языка и стандартные библиотеки, а виртуальную машинуJava (JVM) для вашей ОС.

Для того чтобы скачать и установить JDK открываем браузер, и в строке поиска Google вводим download JDK или переходим по этой ссылке.

Скролим ниже и находим таблицу с вариантами скачивания JDK. В зависимости от нашей операционной системы выбираем файл для скачивания.

Процесс установки для ОС Windows имеет несколько этапов. Не стоит пугаться, все очень просто и делается в несколько кликов. Вот здесь подробно описан процесс установки. Самое важное для пользователей Windows это добавить системную переменную JAVA_HOME. В этой же статье достаточно подробно расписано как это сделать (есть даже картинки).

Для пользователей MacOS также стоит добавить переменную JAVA_HOME. Делается это следующим образом. После установки .dmg файла JDK переходим в корневую папку текущего пользователя и находим файл .bash_profile. Если у вас уже стоит zsh то ищем файл .zshenv. Открываем этот файл на редактирование и добавляем следующие строки:

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Homeexport PATH=${PATH}:${JAVA_HOME}

Здесь обратите внимание на версию JDK указанную в пути - jdk1.8.0_271.jdk. Могу предположить, что у вас она будет отличаться, поэтому пройдите по указанному пути и укажите свою версию. Сохраняем изменения и закрываем файл, он нам больше не понадобится.

Теперь важно проверить правильность установки JDK. Для этого открываем командную строку, в случае работы на Windows, или терминал для MacOS. Вводим следующую команду: java -version Если вы все сделали правильно, вы увидите версию установленного JDK. В ином случае вы, скорее всего, допустили где-то ошибку. Советую внимательно пройтись по всем этапам установки.

Установка IDE

Теперь нам нужно установить среду разработки, она же IDE (Integrated development environment). Что собой представляет среда разработки? На самом деле она выглядит как текстовый редактор, в котором мы можем вводить и редактировать текст. Но помимо этого, этот текстовый редактор умеет делать проверку синтаксиса языка на котором вы пишете. Делается это для того чтобы на раннем этапе подсказать вам о том, что вы допустили ошибку в своем коде.

Также среда разработки содержит в себе компилятор. Компилятор - это специальный инструмент, который будет превращать код, который вы пишете, в машинный код или близкий к машинному коду.

Кроме этого, среда разработки поддерживает отладчики которые помогают править и отлаживать ваш код в случае ошибки. Скажем так, это были описаны основные возможности IDE. Современные IDE предоставляют огромное количество инструментов, которые могут помочь в написании, отладке, автоматической генерации кода и решить множество других проблем.

Для начала нам нужно выбрать и среду разработки. Их довольно таки много, и самыми популярными из них являются: IntelliJ IDEA, NetBeans, Eclipse. Для себя я выбираю IntelliJ IDEA. Она является самой удобной на мой взгляд, и хоть она и платная, на официальном сайте можно найти бесплатную версию которая называется Community. Этой версии будет вполне достаточно для изучения основ Java. Вообщем будем работать в IntelliJ IDEA.

Итак, открываем браузер, в поисковой строке вводим "Download IntelliJ IDEA Community" или переходим по этой ссылке. Выбираем версию ОС и качаем версию Community.

В установке IntelliJ IDEA нет ничего военного. На крайний случай на ютубе есть множество видео о том, как установить эту программу.

Первая программа

Теперь мы готовы создать нашу первую программу. В окошке запустившийся IDE нажимаем New Project.

В новом окошке в левой панели выбираем Java.

Обратите внимание! В верхнем окошке, справа, возле надписи "Project SDK:" должна находится версия Java, которую вы установили вместе с JDK. Если там пусто, то вам нужно будет указать путь к вашему JDK вручную. Для этого в выпадающем списке нажмите "Add JDK..." и укажите путь к вашему JDK, который был предварительно установлен.

Теперь можем нажать на кнопку Next. В следующем окошке, вверху, поставьте галочку Create project from template и выберите Command Line App. И снова нажимаем Next.

Дальше нам нужно указать имя программы. У меня это будет Hello World, желательно чтобы имя проекта было введено латиницей, и на английском языке.

Примечание. Все программы, имена программ, принято писать на английском языке, и желательно придерживаться такого стиля, что является хорошим тоном в программировании.

После указываем путь к проекту программы.

Далее, нам нужно указать базовый пакет нашей программы. О пакетах я расскажу вам позже, обычно компании используют свое имя Интернет-домена в обратном порядке, но вы можете написать, например, свои имя и фамилию через точку в нижнем регистре (маленькими буквами), тоже латиницей. Я же использую псевдоним. Когда все поля будут заполнены - нажимаем Finish.

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

Это окно, то что вы будете видеть 80-90%, а иногда и 100% времени, работая программистом.

Для того чтобы закончить ваше первое приложение, останется добавить строчку кода System.out.print("Hello world!"); как показано на скриншоте.

Чтобы скомпилировать и запустить на выполнение вашу программу, вам нужно нажать кнопочку с зеленым треугольничком на верхней панели справа, или в меню найти пункт Run -> Run Main. И внизу на нижней панели, под окном редактора, в консоли, вы увидите результат выполнения вашей программы. Вы увидите надпись Hello World! Поздравляю, вы написали свою первую программу на Java.

Разбираем первую программу

В своем первом приложении вы можете увидеть много непонятных символов и слов, но на данном этапе вы должны воспринять их как данность, позже, в следующих частях, я расскажу о каждом из них, и зачем они нужны. На данном этапе вам нужно понять что это стандартные составляющие любого Java-приложения, и в последующих приложениях эти компоненты будут изменяться минимально.

Пройдемся по порядку:

В начале мы видим package com.zephyr.ventum; - это объявление пакета, и это постоянный атрибут файлов с исходным кодом в Java. Простыми словами, это локация вашего файла в проекте и любой .java файл должен начинаться с подобной строки.

Ниже, public class Main { - это стандартное объявление класса в Java, где public - это модификатор доступа который дает программисту возможность управлять видимостью членов класса, class - является ключевым словом объявляющим класс, Main - это имя класса. Все определение класса и его членов должно располагаться между фигурными скобками { }. Классы мы рассмотрим немного позже, только скажу что в Java все действия программы выполняются только в пределах класса.

Ключевое слово - это слово зарезервированное языком программирования. Например, package - это тоже ключевое слово.

Еще ниже, public static void main(String[] args) { - эта строка является объявлением метода main. Метод (или часто говорят функция) main это точка входа в любой java-программер. Именно отсюда начинается выполнение вашего кода. В проекте может быть несколько методов main, но мы должны выбрать какой-то один для запуска нашей программы. В следующих статьях мы еще вернемся к этому. Сейчас же у нас только один метод main.

Фигурные скобки {} у метода main обозначаю начало и конец тела метода, весь код метода должен располагаться между этими скобками. Аналогичные скобки есть и у класса Main.

Следующая строка является // write your code here однострочным комментарием.

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

Многострочный комментарий будет выглядеть следующим образом:

/* write  your  code here */

Мы просто располагаем несколько строк между символами /* и */

System.out.print("Hello world!"); - строка которая находится внутри метода main является командой, которая выводит в консоль строку "Hello world!"

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

Затем мы закрываем тело нашего метода main } а также закрываем класс Main }.

На этом статья подходит к концу. Автором конкретно этого материала является Егор и все уменьшительно ласкательные формы слов сохранились в первозданном виде.

В следующей статье мы поговорим о типах данных в Java.

Подробнее..

Перевод Приключения с Ansible уроки, извлеченные из практики

12.04.2021 20:16:10 | Автор: admin

В преддверии старта Экспресс-курса IaC Ansible делимся с вами переводом материала.


Ansible это мощный инструмент ИТ-автоматизации и, аналогично другим подобным инструментам, его освоение требует времени.

Я использовал Ansible для автоматизации развертывания и управления корпоративными приложениями и извлек ряд уроков, с которыми хотел бы поделиться, чтобы помочь другим. В конце концов, это open source принцип.

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

Для того чтобы уйти от этого и научиться быстрым, регулярным и автоматизированным деплоям, требуются как технические, так и культурные изменения. О культурных аспектах я расскажу в другом посте, а здесь я хотел бы остановиться на некоторых практиках Ansible, которые использовал.

Стандарты

Для автоматизации важны три вещи:

  1. стандарты

  2. стандарты и

  3. ах да, стандарты!

Довольно просто написать небольшой плейбук для выполнения конкретной админской задачи. Но при разработке сотни плейбуков / ролей для автоматического развертывания целого программного стека в разных окружениях все становится довольно сложным. Кроме того, ваши плейбуки и роли будут усложняться, если вы не будете переиспользовать роли и не стандартизируете их. Начать изучение Ansible вы можете с этого туториала (англ.).

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

Далекая-далекая галактика

Ansible Galaxy, централизованный репозиторий ролей Ansible от сообщества ваш друг. Вам обязательно стоит посмотреть не решил ли уже кто-то другой вашу проблему.

Но будьте осторожны. Содержимое Galaxy использовать можно, но его следует внимательно изучить и разобраться, прежде чем размещать в своем окружении. Вы легко и быстро можете испортить себе жизнь, используя роль, которая делает что-то неожиданное.

Инвентори

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

Например, у нас было много XML-файлов, содержащих информацию о конфигурации нашего программного обеспечения. Мы создали шаблоны Jinja и перенесли конфигурацию из XML-файлов в Ansible Inventory. XML-файлы стало гораздо проще генерировать и деплоить на серверы. Кроме того, для работы с разными окружениями мы использовали специально созданный Vars Plugin, написанный на Python, загружающий только нужные данные конфигурации в инвентори.

Обработчики

Обработчики (handlers) это задачи, которые запускаются по событию. Только по событию и только один раз для плейбука. Например, вы можете настроить обработчики для такого события, как перезапуск сервиса или приложения. Использование обработчиков в роли, управляющей определенным сервисом, может быть отличным решением для создания ясных и понятных задач в файле handlers / main.yml, запускаемых при изменении сервиса.

Когда я начинал работать с Ansible, то обработчиков в нем еще не было. Так что я могу рассказать вам, какой была жизнь до и после этого не позволяйте этой возможности пропадать впустую!

Идемпотентность поймите ее, живите с ней, любите ее

Идемпотентность это образ жизни ИТ-автоматизации. Вам нужно понять ее, а затем при написании кода автоматизации жить в соответствии с ней. Так что же это такое? Процитирую глоссарий Ansible, операция "идемпотентна, если результат однократного применения операции такой же, как результат ее многократного применения".

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

Что в имени твоём?

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

  1. Не пишите в плейбуках Ansible комментарии как при разработке программного обеспечения, например, "# это комментарий". Помните, Ansible это не язык программирования, это инструмент автоматизации.

  2. Вместо этого для описания Play и Task используйте "Name" с понятным названием. Опишите свои намерения, но постарайтесь воздержаться от технических деталей.

Самая большая польза от свойства "Name" заключается в том, что оно отображается при запуске плейбука. Это помогает при диагностике. А комментарии не отображаются.

Мой опыт показывает, что плейбуки / роли / задачи пишутся разными людьми в компании (по крайней мере, командами Dev и Ops), и все должны понимать, что делает задача.

Именование и приоритет переменных

Будьте осторожны при именовании переменных в инвентори, плейбуках и ролях Ansible. Помните, что Ansible присваивает каждую переменную определенному хосту (Host Variables). Поэтому с осторожностью используйте общие имена, такие как "java_path: /my path". Есть вероятность, что другое приложение на том же хосте использует Java, но по другому пути. Часто на одном хосте могут размещаться несколько приложений, использующих разные версии Java, расположенные по разным путям.

Указывайте в переменных префикс, используя название вашей компании. Например, переменные приложения AppOne, созданного компанией ABC и использующее Java 1.8, могут выглядеть следующим образом:

abc_appone_java_path: "/opt/appone/java"abc_appone_java_version: "1.8"

Не копипастите

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

Используйте IDE

Используйте для Ansible какую-нибудь IDE. В конце концов, это просто YAML. IDE должна упрощать вам работу. Вот некоторые функции, которые я считаю критически важными для IDE при создании ролей и плейбуков Ansible:

  • Подсветка синтаксиса YAML и поддержка линтеров.

  • Подсветка синтаксиса Python и поддержка линтеров Ansible написан на Python, и, поверьте мне, вам иногда придется смотреть исходный код.

  • Интеграция с системой контроля версий.

  • Интеграция с JIRA возможность управлять изменениями сразу в нескольких тикетах.

  • Удобные поиск и замена часто вам нужно будет изменять, удалять или перемещать какую-то переменную сразу в нескольких файлах.

  • Поддержка Ruby если вы также собираетесь работать с Vagrant.

  • Поддержка Groovy если вы собираетесь работать с заданиями Jenkins.

Эти советы помогут вам быть на шаг впереди и избежать тех ошибок, которые совершал я при использовании Ansible для развертывания и автоматизации корпоративного программного обеспечения. У вас будут свои новые и интересные ошибки! Для дополнительной информации о лучших практиках работы с Ansible смотрите раздел лучших в практик документации Ansible и скачайте чеклист по автоматизации, а также следите за блогами Red Hat и Ansible.


Узнать подробнее про Экспресс-курс IaC Ansible.

Подробнее..

Влияние протокола языкового сервеар (LSP) на будущее IDE

02.05.2021 20:23:47 | Автор: admin

Перевод статьи How the Language Server Protocol Affects the Future of IDEs

Автор оригинала Mehul Mohan

С момента своего появления Visual Studio Code в одиночку так сильно повлиял на экосистему разработчиков, что возврата назад уже не будет. Это общедоступный бесплатный инструмент с открытым исходным кодом и это очень мощный инструмент.

Но, благодаря VSCode, Microsoft в 2016 году дала жизнь еще одной супер значимой вещи, которая менее известна. Это Language Server Protocol - Протокол языкового сервера.

Что такое Протокол языкового сервера?

Протокол языкового сервера (Language Server Protocol - LSP) - это способ общения с языковыми серверами (такой же как HTTP или FTP).

Языковые серверы - это специальные программы, работающие на обычных серверах. Они принимают мета-состояние редактора, в котором вы пишете код (например, текущая позиция курсора внутри редактора, токен над которым находится курсор), и возвращают набор действий или инструкций - какой токен должен быть следующим, что должно произойти, когда вы нажимаете CMD/Ctrl-клик на этом токене, и так далее.

Это взаимодействие происходит с помощью набора правил, заданных протоколом. Протокол языкового сервера можно рассматривать как урезанную модификацию HTTP взаимодействующую только по JSON-RPC протоколу.

Зачем нужен LSP?

Вы заметили, что в VSCode постоянно появляются изобретательные сообщения об общибках и предложения автоподстановок? И как же легко, просто установив расширение из магазина VSCode, вы получаете всю мощь IntelliSense для совершенно разных языков, таких как C, Python, Java и т.д.? Все это происходит благодаря LSP.

Поддержка автозавершения и IntelliSense для HTML/CSS/JavaScript идет сразу вместе с VSCode (так же, как PyCharm идет сразу с поддержкой Python). Однако такая же поддержка других языков может быть реализована с помощью протокола LSP для этих языков.

Что такое JSON-RPC?

JSON-RPC означает удаленный вызов процедуры JSON (Remote Procedure Call). Это архитектура (подобно тому, как REST является архитектурой), но основная единица - это вызов процедуры, а не конечная точка API в случае REST.

Это простой пример для JSON-RPC:

// Requestcurl -X POST data '{"jsonrpc": "2.0","method": "runThisFunction","params": [ "some-param", 2 ],"id": 1}'// Response{  "jsonrpc": "2.0",  "result": "codedamn",  "id": 1}

В этом примере мы посылаем запрос в кодировке JSON в соответствии со спецификацией RPC. Если сервер настроен на корректную работу с JSON-RPC, то он выполнит метод runThisFunction с переданными параметрами и вернет результат в том виде, как показано выше.

LSP + JSON-RPC

LSP использует JSON-RPC для связи с удаленным сервером. Для этого используется следующий формат:

Content-Length: <bytes of JSON>\r\n\r\n<json-payload>

Пример запроса:

Content-Length: 78{"jsonrpc":"2.0","method":"runThisFunction","params":["some-param",2],"id":1}

LSP требует, чтобы вы передали заголовок Content-Length, за которым следуют два CRLF токена \r\n. Когда запущенные языковые серверы, такие как ccls, получат это сообщение, они ответят соответствующим сообщением:

Конечно, в приведенном выше примере видно, что ccls говорит о том, что не существует метода, названного runThisFunction. Но можно заметить, что удаленный сервер также отвечает заголовком Content-Length со спецификацией JSON-RPC.

Почему все это так важно?

С введением формального протокола LSP, Microsoft свела знаменитую проблему M x N к проблеме M + N.

M = Различные языки (C, C++, PHP, Python, Node, Swift, Go и т.д.).

N = Различные редакторы кода (VSCode, Eclipse, Notepad++, Sublime Text и т.д.).

Раньше для того, чтобы M редакторов поддерживали N языков, вам нужно было иметь M*N решений. То есть каждый редактор кода должен был реализовать поддержку каждого языка самостоятельно.

С появлением LSP в редактор оставалось лишь внедрить поддержку протокола языкового сервера. После этого любой, кто делает языковой сервер (следуя стандартам LSP), может легко интегрироваться в редактор кода, при этом редактор никогда не будет "знать", с каким языком он работает!

Будущее IDE

По мере того, как языковые сервера реализуются для различных языков программирования, у разработчиков появляется возможность выбирать редактор на свой вкус. Без привязки к конкретному языку. Нет больше необходимости ограничивать себя, например только XCode для разработки на Swift или PyCharm для Python. И не только это, LSP можно внедрить прямо в JavaScript для поддержки IntelliSense в браузере! Настало потрясающее время для программистов!

Подробнее..
Категории: Программирование , Ide , Lsp , Vs

Перевод Влияние протокола языкового сервера (LSP) на будущее IDE

03.05.2021 00:11:12 | Автор: admin

Перевод статьи How the Language Server Protocol Affects the Future of IDEs

Автор оригинала Mehul Mohan

С момента своего появления Visual Studio Code в одиночку так сильно повлиял на экосистему разработчиков, что возврата назад уже не будет. Это общедоступный бесплатный инструмент с открытым исходным кодом и это очень мощный инструмент.

Но, благодаря VSCode, Microsoft в 2016 году дала жизнь еще одной супер значимой вещи, которая менее известна. Это Language Server Protocol - Протокол языкового сервера.

Что такое Протокол языкового сервера?

Протокол языкового сервера (Language Server Protocol - LSP) - это способ общения с языковыми серверами (такой же как HTTP или FTP).

Языковые серверы - это специальные программы, работающие на обычных серверах. Они принимают мета-состояние редактора, в котором вы пишете код (например, текущая позиция курсора внутри редактора, токен над которым находится курсор), и возвращают набор действий или инструкций - какой токен должен быть следующим, что должно произойти, когда вы нажимаете CMD/Ctrl-клик на этом токене, и так далее.

Это взаимодействие происходит с помощью набора правил, заданных протоколом. Протокол языкового сервера можно рассматривать как урезанную модификацию HTTP взаимодействующую только по JSON-RPC протоколу.

Зачем нужен LSP?

Вы заметили, что в VSCode постоянно появляются изобретательные сообщения об общибках и предложения автоподстановок? И как же легко, просто установив расширение из магазина VSCode, вы получаете всю мощь IntelliSense для совершенно разных языков, таких как C, Python, Java и т.д.? Все это происходит благодаря LSP.

Поддержка автозавершения и IntelliSense для HTML/CSS/JavaScript идет сразу вместе с VSCode (так же, как PyCharm идет сразу с поддержкой Python). Однако такая же поддержка других языков может быть реализована с помощью протокола LSP для этих языков.

Что такое JSON-RPC?

JSON-RPC означает удаленный вызов процедуры JSON (Remote Procedure Call). Это архитектура (подобно тому, как REST является архитектурой), но основная единица - это вызов процедуры, а не конечная точка API в случае REST.

Это простой пример для JSON-RPC:

// Requestcurl -X POST data '{"jsonrpc": "2.0","method": "runThisFunction","params": [ "some-param", 2 ],"id": 1}'// Response{  "jsonrpc": "2.0",  "result": "codedamn",  "id": 1}

В этом примере мы посылаем запрос в кодировке JSON в соответствии со спецификацией RPC. Если сервер настроен на корректную работу с JSON-RPC, то он выполнит метод runThisFunction с переданными параметрами и вернет результат в том виде, как показано выше.

LSP + JSON-RPC

LSP использует JSON-RPC для связи с удаленным сервером. Для этого используется следующий формат:

Content-Length: <bytes of JSON>\r\n\r\n<json-payload>

Пример запроса:

Content-Length: 78{"jsonrpc":"2.0","method":"runThisFunction","params":["some-param",2],"id":1}

LSP требует, чтобы вы передали заголовок Content-Length, за которым следуют два CRLF токена \r\n. Когда запущенные языковые серверы, такие как ccls, получат это сообщение, они ответят соответствующим сообщением:

Конечно, в приведенном выше примере видно, что ccls говорит о том, что не существует метода, названного runThisFunction. Но можно заметить, что удаленный сервер также отвечает заголовком Content-Length со спецификацией JSON-RPC.

Почему все это так важно?

С введением формального протокола LSP, Microsoft свела знаменитую проблему M x N к проблеме M + N.

M = Различные языки (C, C++, PHP, Python, Node, Swift, Go и т.д.).

N = Различные редакторы кода (VSCode, Eclipse, Notepad++, Sublime Text и т.д.).

Раньше для того, чтобы M редакторов поддерживали N языков, вам нужно было иметь M*N решений. То есть каждый редактор кода должен был реализовать поддержку каждого языка самостоятельно.

С появлением LSP в редактор оставалось лишь внедрить поддержку протокола языкового сервера. После этого любой, кто делает языковой сервер (следуя стандартам LSP), может легко интегрироваться в редактор кода, при этом редактор никогда не будет "знать", с каким языком он работает!

Будущее IDE

По мере того, как языковые сервера реализуются для различных языков программирования, у разработчиков появляется возможность выбирать редактор на свой вкус. Без привязки к конкретному языку. Нет больше необходимости ограничивать себя, например только XCode для разработки на Swift или PyCharm для Python. И не только это, LSP можно внедрить прямо в JavaScript для поддержки IntelliSense в браузере! Настало потрясающее время для программистов!

Подробнее..
Категории: Программирование , Ide , Lsp , Vs

Представляем OpenShift Pipelines

27.05.2021 16:21:56 | Автор: admin

3 мая 2021 года Red Hat выпустила первую общедоступную версию OpenShift Pipelines, облачно-ориентированной системы непрерывной интеграции на базе СПО-проекта Tekton. Решение реализует Kubernetes фреймворк CI/CD для разработки и выполнения конвейеров, в которых каждый шаг запускается в своем собственном контейнере, что позволяет масштабировать шаги независимо друг от друга. Сегодня мы вкратце рассмотрим ключевые особенности и преимущества этого решения, а также приведем список дополнительных ресурсов для дальнейшего знакомства с ней и освоения.

Но, прежде чем переходить к OpenShift Pipelines, освежим в памяти основные концепты Tekton.

Основные концепты Kubernetes-нативной CI/CD

OpenShift Pipelines дополняет Kubernetes/OpenShift соответствующими CRD (ресурсами, определяемыми пользователем) для концептов CI/CD, таких как конвейер (pipeline), задача (task), шаг (step). В результате, эти концепты становятся своими (native) инстанцируемыми их можно создавать в виде отдельных экземпляров и, как следствие, полноценно масштабировать и развертывать, а также обеспечивать их безопасность средствами Kubernetes.

Поэтому для начала вспомним, что такое концепты Tekton:

Рис. 1. КонцептыTektonРис. 1. КонцептыTekton

По сути, основные концепты Tekton делятся на два вида: те, что задают конвейер, и те, что запускают конвейер.

Концепты, задающие конвейер (define pipeline)

  • Task повторно используемые и слабо связанные серии шагов (step), которые выполняют определенную задачу, например, сборку контейнерного образа.

  • Pipeline описание конвейера и задач (Task), которые он должен выполнять.

Концепты, запускающие конвейер (run pipelines)

  • TaskRun запуск и результаты выполнения экземпляра Task.

  • PipelineRun запуск и результаты выполнения экземпляра конвейера, который включает в себя ряд концептов TaskRun.

Подробнее об этих концептах можно почитать в официальной документации.

Теперь разберемся, что такое OpenShift Pipelines и для чего он нужен

Что такого особенного в OpenShift Pipelines?

OpenShift Container Platform это ведущая отраслевая Kubernetes-платформа корпоративного класса, которая предоставляет разработчикам множество функций, среди которых есть и CI/CD.

OpenShift Pipelines базируется на СПО-проекте Tekton и расширяет функционал платформы OpenShift стандратными методами, что сильно облегчает жизнь разработчикам.

Установка OpenShift Pipelines через механизм Operator

OpenShift Pipelines поддерживается на уровне механизма операторов, поэтому он легко устанавливается и обновляется, и, соответственно, легко администрируется.

OpenShift Pipelines доступен на сайте OperatorHub, где представлены более 450 различных операторов для OpenShift Container Platform:

Установка OpenShift Pipelines предельно проста, и он сразу устанавливается как оператор уровня кластера, автоматически становясь доступным для всех проектов:

OpenShift Pipelines также дополняет OpenShift соответствующими CR, которые позволят добиться еще большей универсальности в плане конфигурации и интеграции с консолью OpenShift и т.д.

При появлении в OperatorHub новой версии OpenShift Pipelines, вы как администратор можете выбрать обновление до следующей версии, задав нужный канал обновления.

Развитый UI в рамках консоли OpenShift

Tekton тоже дополняет стандартную поставку OpenShift концептами CI/CD, но в нем при создании и запуске конвейеров сложно обойтись без создания YAML-кода, и этого кода требуется писать очень много, тысячи строк. Поэтому Red Hat реализовала в консоли OpenShift полноценный UI для запуска и визуализации конвейеров (как тех, что работают сейчас, так и тех, что уже отработали), а также для графического создания конвейеров. При этом все необходимые YAML-файлы создаются автоматически, без написания какого-либо кода.

Ниже показано графическое представление конвейера, который уже был выполнен на платформе OpenShift, причем прямо из него можно получить доступ к журналам и событиям всех задач этого конвейера:

Рис. 2. Конвейеры в консоли OpenShiftРис. 2. Конвейеры в консоли OpenShift

При желании можно легко просмотреть полный лог выбранной задачи:

Чтобы еще больше облегчить жизнь разработчикам, OpenShift Pipelines позволяет рисовать конвейеры прямо в консоли OpenShift, поэтому вам больше не нужен черный пояс по YAML, чтобы создать свой первый конвейер Tekton:

Рис. 3. Графическое проектирование конвейера в консоли OpenShiftРис. 3. Графическое проектирование конвейера в консоли OpenShift

Но если вы, как обладатель черного пояса по YAML, захотите что-то подправить, это всегда можно сделать прямо из консоли OpenShift:

Рис. 4. YAML примеры и снипеты в консоли OpenShiftРис. 4. YAML примеры и снипеты в консоли OpenShift

Более того, OpenShift Pipelines пригодится, даже если вы решите пойти по пути чистого YAML, и предложит вам готовые примеры и снипеты кода для более быстрой разработки конвейеров YAML. Кроме того, в систему можно интегрировать ваши корпоративные снипеты, сделав их доступными всем разработчикам. Именно для этого мы и добавили специальный CRD под названием ConsoleYAMLSamples.

События как триггеры для запуска конвейеров

Хотите увязать запуск конвейеров с некими внешними событиями (в терминах Tekton это называется Trigger), например, push- или pull-запросами Github или Gitlab? Вообще не проблема, в OpenShift Pipelines это есть из коробки, причем поддерживаются различные вендоры, включая Github, Gitlab, BitBucket и т.д.

Рис. 5. Добавление триггера в консоли OpenShiftРис. 5. Добавление триггера в консоли OpenShift

Вы просто создаете нужный триггер в UI, а OpenShift сам формирует все необходимые концепты, такие как EventListeners, TriggerTemplates (подробнее о них можно почитать в официальной документации).

Готовые повторно используемые задачи и кастомизация

OpenShift Pipelines из коробки содержит десятки готовых задач, которые можно использовать в составе конвейеров, включая задачи по клонированию кода из репозитория, сборки приложений на различных языках программирования, таких как java, dotnet core, python go, nodejs или использования инструментов сборки вроде maven, развертывания приложений и т.д. Список доступных задач можно увидеть в консоли OpenShift, раздел ClusterTasks, меню Pipelines -> Tasks:

Рис. 6. OpenShift Pipelines из коробки предлагает десятки готовых задачРис. 6. OpenShift Pipelines из коробки предлагает десятки готовых задач

Кроме того, этот список можно легко расширить. Для этого достаточно добавить ClusterTasks в кластер, после чего вам станут доступны сотни дополнительных задач на сайте TektonHub, который представляет собой публичный репозиторий для обмена задачами Tekton:

Рис. 7.TektonHub публичный репозиторий повторно используемых задач и конвейеров TektonРис. 7.TektonHub публичный репозиторий повторно используемых задач и конвейеров Tekton

Интеграция с IDE

Разработчики, использующие командную строку и IDE, могут воспользоваться преимуществами Tekton CLI, расширения Tekton для Visual Studio Code и плагина Tekton для IntelliJ, чтобы взаимодействовать с конвейерами прямо из своей обычной среды разработки и создавать, запускать, просматривать и выполнять действия на кластере непосредственно из командной строки.

Рис. 8. Расширение VSCode для OpenShift PipelinesРис. 8. Расширение VSCode для OpenShift Pipelines

Полезные ресурсы

В заключение советуем посмотреть видеоверсию этой статьи на английском:

А также рекомендуем следующие ресурсы (EN):

Видеоролики на русском:

Вебинары:

Подробнее..

DataGrip 2020.3 SQL для Монги, новые форматы экспорта, интроспекция прав доступа и другое

25.11.2020 18:12:55 | Автор: admin
Привет! Очередной длинный пост о том, что мы сделали за последние четыре месяца. Как всегда, мы говорим DataGrip, а подразумеваем все остальные наши IDE. В том числе и WebStorm, SQL-плагин к которому теперь можно докупить.




Самое важное:


SQL для MongoDB
Поддержка Couchbase
Аутентификация через Azure AD
Улучшения в редакторе больших значений
Открытие таблиц в транспонированном виде
Новые форматы экспорта
Интроспекция прав доступа
Форматирование диалекта Generic
Улучшения в конфигурациях запуска
Перетаскивание вкладок

SQL для MongoDB


Теперь можно писать SQL в MongoDB. Мы написали транслятор SQL в JavaScript.



Работают только запросы SELECT и предложения JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET. Полный пост обо всём, что мы поддержали, читайте здесь.

В контекстном меню есть две опции: Copy JS script to clipboard и Show JS Script. Последняя откроет окно с JS-запросом, который мы отправим на сервер. Здесь же можно его отредактировать и запустить.



Соединение


Поддержка Couchbase


Наша семья растёт: мы поддержали Couchbase! Важно отметить, что DataGrip работает с Couchbase Query, а не с Couchbase Analytics.



Аутентификация через Azure AD


Об этом нас давно просили: к базе данных Azure SQL теперь можно подсоединиться через Active Directory.



Рабочая папка


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



Редактор данных


Редактор больших значений


Мы сделали этот редактор в предыдущей версии, а сейчас улучшили несколько вещей.

Форматированное значение


Если в ячейке хранится однострочный XML или JSON, в редакторе значений он будет показан в отформатированном виде. Причем вы можете отредактировать значение в удобном виде, а сохранится оно всё равно как одна строка.



Расположение снизу


Если вам важна ширина экрана, передвиньте редактор вниз.



Картинки


Ещё в этом же редакторе отображаются картинки.



Открытие таблиц в транспонированном виде


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



Выделение в редакторе


В редакторе всегда работало умное выделение: нажимаете Ctrl+W на Windows/Linux или Opt+Up на macOS и сначала выделяется текущая строка, потом текущий столбец, потом вся таблица. Теперь можно двигаться и в обратном порядке: при помощи Ctrl+Shift+W на Windows/Linux и Opt+Down на macOS.



Импорт/экспорт


Новые форматы


Сделали два новых формата: One-row и SQL-Insert-Multirow.



One-Row копирует выделенные значения в одну строку через запятую. Это удобно, когда значения столбца нужно вставить в оператор IN.



SQL-Insert-Multirow сгенерирует один INSERT для нескольких строк. В некоторых базах, например MySQL и PostgreSQL, это работает.



Не ставить кавычки


Новая опция в CSV форматах: never quote values.



Интроспекция


Сбор диагностической информации


Интроспекция это процесс сбора информации о базе данных. Эту информацию DataGrip хранит у себя, чтобы помогать вам навигироваться по базе, искать объекты, дополнять код.

Иногда при интроспекции обнаруживаются разные проблемы. Мы не всегда можем их воспроизвести. Поэтому мы придумали механизм сбора данных, которые пригодятся, если вы пишете нам в поддержку или трекер.

Diagnostic refresh запустит интроспекцию, соберет нужную информацию и запишет её в файл, который откроется в файловом менеджере. Он пригодится нам, чтобы понять, что происходит.

Prepare introspector diagnostic собирает всю возможную информацию, не перезапуская интроспекцию. Результат работы этого действия присылайте, если что-то устарело или не подгрузилось.



Права доступа


DataGrip теперь знает о правах доступа и показывает их в сгенерированном DDL для объекта.


Изменённые объекты в проводнике базы данных


Если вы изменили DDL объектов, но ещё не успели отправить свои изменения в базу, эти объекты будут подсвечены в дереве. Раньше изменённые объекты показывались только в окне Database Changes.



[PostgreSQL] Больше свойств таблиц


DataGrip генерирует DDL таблицы, включая TABLESPACE и INDEX ACCESS METHOD.



[Greenplum] Новые объекты


В дереве базы данных Greenplum стало больше отображаемых объектов: мы добавили коллации, материализованные представления, внешние таблицы, обёртки сторонних данных, сторонние сервера, сторонние таблицы и сопоставления пользователей.



[Oracle] Корректное отображение перегрузок


Перегрузки внутри одного пакета раньше отображались как один объект. Теперь как несколько. Число в квадратных скобках это индекс процедуры в базе данных.



Помощь в написании кода


Форматирование диалекта Generic


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



Новый интерфейс переименования


Внутристрочное переименование теперь предлагает опции: переименовать в комментариях, строках и в текстовых вхождениях. Текстовые вхождения это обычный поиск по тексту в файлах, формат которых мы не подсвечиваем.



Исправление опечаток


Alt+Enter на Windows/Linux и Opt+Enter на macOSтеперь сразу предлагают список исправлений, без вложенного меню.



[Oracle] Конфликт вызовов


Если возникает конфликт вызова перегруженной процедуры, DataGrip предупредит об этом.



Новые настройки капитализации в форматировании


Теперь можно указать капитализацию отдельно для встроенных типов, пользовательских типов и встроенных функций.



Запуск запросов


Конфигурации запуска


Запуск скриптов из файлов стал удобнее:

Смена контекста


DataGrip предупредит, если внутри скрипта меняется контекст, то есть используется USE или SET search_path.



Отображение контекста


Если вы выбрали накатить скрипт на источник данных, то DataGrip выберет схему по умолчанию. Теперь она отобразится рядом серым цветом.



История запуска


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



Вывод консоли в отдельной вкладке


Если для вас важна ширина экрана, вывод консоли можно открывать в новой вкладке окна Services по умолчанию. Таким образом, вся ширина экрана будет задействована для результата запроса или текстового вывода. Включается это в Settings/Preferences | Database | General | Open new services tab for sessions.



[SQL Server] Имена вкладок


В прошлом году мы сделали так, что комментарии перед запросом становятся именами вкладок. Это не работало в SQL Server, если запускать сразу несколько запросов, потому что SQL Server обрабатывает несколько запросов как один. Теперь работает как надо.



[SQL Server] Подсказки для создания индекса в планировщике


В планировщике теперь появляются подсказки, которые предлагают создать индекс для улучшения производительности запроса.



Редактирование DDL



Предупреждение о неактуальной структуре таблицы


Если вы открываете DDL таблицы, но кэшированная версия DataGrip успела устареть, вас об этом предупредят. Раньше это работало только для объектов с исходным кодом: процедур, функций, представлений.



Предупреждение, если объект удален


Если объект был удален, пока вы его редактировали, появится сообщение об этом. Например, вы вносили изменения в процедуру, а в это время её кто-то дропнул!



DataGrip предложит три варианта:

Revert local changes: выбирайте этот вариант, если вас устраивает, что процедура удалена. Редактор закроется.

Keep local changes: в этом варианте вы будете продолжать вносить изменения в процедуру, а при нажатии Submit она будет создана заново.

Restore in the database: то же самое, но DataGrip сразу же восстановит вариант процедуры, который был актуален на тот момент, когда вы ее открыли. Это нужно для того, чтобы правильно подсветить ваши текущие изменения при редактировании. После нажатия Submit результаты этого действия и Keep local changes будут одинаковы.

Действия по работе с исходниками


Действия Submit, Rollback и Show Changes теперь можно запускать для объекта из любого контекста, в том числе проводника. Например, у вас висят изменения для некоторого количества процедур. Можно выделить несколько из них в дереве и только их исходники отправить в базу. А остальные, например, откатить. Раньше эти операции делались только из окна Database Changes.



Общее


Перетаскивание вкладок


Теперь можно переносить вкладки в разные области экрана. Получается, есть и полностью независимый сплит-режим: если выбрать режим In-Editor Results, у каждой вкладки будет свой результат запроса.



Синхронизация темы с системными настройками


Если выбрать Sync with OS в настройках Settings/Preferences | Appearance & Behavior | Appearance | Theme, то IDE будет автоматически синхронизировать цветовую тему с настройками операционной системы.



Сочетания клавиш macOS словами


Некоторым людям сложно читать условные обозначения клавиш на macOS. Теперь можно дёрнуть ключ ide.macos.disable.native.shortcut.symbols в реестре, и вместо значков будут слова. Сработает для всех меню внутри IDE, кроме главного, в котором поменять что-то мы бессильны.



Вкладка предпросмотра


В настройках панели Files выберите Enable Preview Tab. Теперь по клику на файл его содержимое видно во вкладке предпросмотра. Если вы начинаете редактировать файл, эта вкладка превращается в обычную.



Простой калькулятор


Вычисляйте несложные штуки в окне Search Everywhere.



Ассоциации с файлами


С этой версии в настройках DataGrip можно указать, какие файлы IDE должна открывать по умолчанию. Настройка находится тут: Settings/Preferences | Settings | Editor | File Types | Associate file types with DataGrip.



Кстати, это могут быть не только файлы, связанные с базами данных. DataGrip вполне подходящий редактор для JSON, HTML или MarkDown*!

*с плагином

Развернуть всё


Раньше в панелях Database и Files была только кнопка Collapse All, которая закрывает все узлы. Мы добавили кнопку Expand All, которая открывает все возможные узлы до последнего уровня. Может пригодится при быстром поиске, когда вы просто печатаете имя объекта, находясь в дереве. Такой поиск ищет только по открытым узлам.



Такой вышел релиз!

Скачать триал на месяц

Фидбек принимаем в комментариях к посту и здесь:

Трекер (это если точно нашли проблему)
Телеграм-канал
Твиттер
Почта

Команда DataGrip
Подробнее..

Из песочницы Редактор кода на Android часть 1

02.07.2020 20:17:59 | Автор: admin

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

Вступление


Привет всем! Судя из названия вполне понятно о чем будет идти речь, но всё же я должен вставить свои пару слов перед тем как перейти к коду.

Я решил разделить статью на 2 части, в первой мы поэтапно напишем оптимизированную подсветку синтаксиса и нумерацию строк, а во второй добавим автодополнение кода и подсветку ошибок.

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

  • Подсвечивать синтаксис
  • Отображать нумерацию строк
  • Показывать варианты автодополнения (расскажу во второй части)
  • Подсвечивать синтаксические ошибки (расскажу во второй части)

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

MVP простой текстовый редактор


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

На этом этапе я так же сделал загрузку/сохранение файлов в память. Код приводить не буду, в интернете переизбыток примеров работы с файлами.

Подсветка синтаксиса


Как только мы ознакомились с требованиями к редактору, пора переходить к самому интересному.

Очевидно, чтобы контролировать весь процесс реагировать на ввод, отрисовывать номера строк, нам придется писать CustomView наследуясь от EditText. Накидываем TextWatcher чтобы слушать изменения в тексте и переопределяем метод afterTextChanged, в котором и будем вызывать метод отвечающий за подсветку:

class TextProcessor @JvmOverloads constructor(    context: Context,    attrs: AttributeSet? = null,    defStyleAttr: Int = R.attr.editTextStyle) : EditText(context, attrs, defStyleAttr) {    private val textWatcher = object : TextWatcher {        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}        override fun afterTextChanged(s: Editable?) {            syntaxHighlight()        }    }    private fun syntaxHighlight() {        // Тут будем подсвечивать текст    }}

Вопрос: Почему мы используем TextWatcher как переменную, ведь можно реализовать интерфейс прямо в классе?

Ответ: Так уж получилось, что у TextWatcher есть метод который конфликтует c уже существующим методом у TextView:

// Метод TextWatcherfun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int)// Метод TextViewfun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int)

Оба этих метода имеют одинаковое название и одинаковые аргументы, да и смысл вроде у них тот же, но проблема в том что метод onTextChanged у TextView вызовется вместе с onTextChanged у TextWatcher. Если проставить логи в тело метода, то увидим что onTextChanged вызовется дважды:


Это очень критично если мы планируем добавлять функционал Undo/Redo. Также нам может понадобится момент, в котором не будут работать слушатели, в котором мы сможем очищать стэк с изменениями текста. Мы ведь не хотим, чтобы после открытия нового файла можно было нажать Undo и получить совершенно другой текст. Хоть об Undo/Redo в этой статье говориться не будет, важно учитывать этот момент.

Соответственно, чтобы избежать такой ситуации можно использовать свой метод установки текста вместо стандартного setText:

fun processText(newText: String) {    removeTextChangedListener(textWatcher)    // undoStack.clear()    // redoStack.clear()    setText(newText)    addTextChangedListener(textWatcher)}

Но вернёмся к подсветке.

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

Сейчас нам важно знать только две вещи:

  1. Pattern определяет что конкретно нам нужно найти в тексте
  2. Matcher будет пробегать по всему тексту в попытках найти то, что мы указали в Pattern

Может не совсем корректно описал, но принцип работы такой.

Т.к я пишу редактор для JavaScript, вот небольшой паттерн с ключевыми словами языка:

private val KEYWORDS = Pattern.compile(    "\\b(function|var|this|if|else|break|case|try|catch|while|return|switch)\\b")

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

Далее с помощью Matcher мы пройдёмся по всему тексту и установим спаны:

private fun syntaxHighlight() {    val matcher = KEYWORDS.matcher(text)    matcher.region(0, text.length)    while (matcher.find()) {        text.setSpan(            ForegroundColorSpan(Color.parseColor("#7F0055")),            matcher.start(),            matcher.end(),            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE        )    }}

Поясню: мы получаем объект Matcher у Pattern, и указываем ему область для поиска в символах (Соответственно с 0 по text.length это весь текст). Далее вызов matcher.find() вернёт true если в тексте было найдено совпадение, а с помощью вызовов matcher.start() и matcher.end() мы получим позиции начала и конца совпадения в тексте. Зная эти данные, мы можем использовать метод setSpan для раскраски определённых участков текста.

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

Итак, запускаем!


Результат соответствует ожиданиям ровно до того момента, пока мы не начнём редактировать большой файл (на скриншоте файл в ~1000 строк)

Дело в том что метод setSpan работает медленно, сильно нагружая UI Thread, а учитывая что метод afterTextChanged вызывается после каждого введенного символа, писать код становится одним мучением.

Поиск решения


Первое что приходит в голову вынести тяжелую операцию в фоновый поток. Но тяжелая операция тут это setSpan по всему тексту, а не регулярки. (Думаю, можно не объяснять почему нельзя вызывать setSpan из фонового потока).

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

Точно! Так и сделаем! Вот только как?

Оптимизация


Хоть я и упомянул что нас заботит только производительность метода setSpan, всё же рекомендую выносить работу RegEx в фоновой поток чтобы добиться максимальной плавности.

Нам нужен класс, который будет в фоне обрабатывать весь текст и возвращать список спанов.
Конкретной реализации приводить не буду, но если кому интересно то я использую AsyncTask работающий на ThreadPoolExecutor. (Да-да, AsyncTask в 2020)

Нам главное, чтобы выполнялась такая логика:

  1. В beforeTextChanged останавливаем Task который парсит текст
  2. В afterTextChanged запускаем Task который парсит текст
  3. По окончанию своей работы, Task должен вернуть список спанов в TextProcessor, который в свою очередь подсветит только видимую часть

И да, спаны тоже будем писать свои собственные:

data class SyntaxHighlightSpan(    private val color: Int,    val start: Int,    val end: Int) : CharacterStyle() {    // можно заморочиться и добавить italic, например, только для комментариев    override fun updateDrawState(textPaint: TextPaint?) {        textPaint?.color = color    }}

Таким образом, код редактора превращается в нечто подобное:

Много кода
class TextProcessor @JvmOverloads constructor(    context: Context,    attrs: AttributeSet? = null,    defStyleAttr: Int = R.attr.editTextStyle) : EditText(context, attrs, defStyleAttr) {    private val textWatcher = object : TextWatcher {        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {            cancelSyntaxHighlighting()        }        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}        override fun afterTextChanged(s: Editable?) {            syntaxHighlight()        }    }    private var syntaxHighlightSpans: List<SyntaxHighlightSpan> = emptyList()    private var javaScriptStyler: JavaScriptStyler? = null    fun processText(newText: String) {        removeTextChangedListener(textWatcher)        // undoStack.clear()        // redoStack.clear()        setText(newText)        addTextChangedListener(textWatcher)        // syntaxHighlight()    }    private fun syntaxHighlight() {        javaScriptStyler = JavaScriptStyler()        javaScriptStyler?.setSpansCallback { spans ->            syntaxHighlightSpans = spans            updateSyntaxHighlighting()        }        javaScriptStyler?.runTask(text.toString())    }    private fun cancelSyntaxHighlighting() {        javaScriptStyler?.cancelTask()    }    private fun updateSyntaxHighlighting() {        // подсветка видимой части будет тут    }}


Т.к конкретной реализации обработки в фоне я не показал, представим что мы написали некий JavaScriptStyler, который в фоне будет делать всё тоже самое что мы делали до этого в UI Thread пробегать по всему тексту в поисках совпадений и заполнять список спанов, а в конце своей работы вернёт результат в setSpansCallback. В этот момент запустится метод updateSyntaxHighlighting, который пройдётся по списку спанов и отобразит только те, что видны в данный момент на экране.

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


Буду ссылаться на эту статью, там автор предлагает использовать примерно такой способ:

val topVisibleLine = scrollY / lineHeightval bottomVisibleLine = topVisibleLine + height / lineHeight + 1 // height - высота Viewval lineStart = layout.getLineStart(topVisibleLine)val lineEnd = layout.getLineEnd(bottomVisibleLine)

И он работает! Теперь вынесем topVisibleLine и bottomVisibleLine в отдельные методы и добавим пару дополнительных проверок, на случай если что-то пойдёт не так:

Новые методы
private fun getTopVisibleLine(): Int {    if (lineHeight == 0) {        return 0    }    val line = scrollY / lineHeight    if (line < 0) {        return 0    }    return if (line >= lineCount) {        lineCount - 1    } else line}private fun getBottomVisibleLine(): Int {    if (lineHeight == 0) {        return 0    }    val line = getTopVisibleLine() + height / lineHeight + 1    if (line < 0) {        return 0    }    return if (line >= lineCount) {        lineCount - 1    } else line}


Последнее что остаётся сделать пройтись по полученному списку спанов и раскрасить текст:

for (span in syntaxHighlightSpans) {    val isInText = span.start >= 0 && span.end <= text.length    val isValid = span.start <= span.end    val isVisible = span.start in lineStart..lineEnd            || span.start <= lineEnd && span.end >= lineStart    if (isInText && isValid && isVisible)) {        text.setSpan(            span,            if (span.start < lineStart) lineStart else span.start,            if (span.end > lineEnd) lineEnd else span.end,            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE        )    }}

Не пугайтесь страшного if'а, он всего лишь проверяет попадает ли спан из списка в видимую область.

Ну что, работает?


Работает, вот только при редактировании текста спаны не обновляются, исправить ситуацию можно очистив текст от всех спанов перед наложением новых:

// Примечание: метод getSpans из библиотеки core-ktxval textSpans = text.getSpans<SyntaxHighlightSpan>(0, text.length)for (span in textSpans) {    text.removeSpan(span)}

Ещё один косяк после закрытия клавиатуры кусок текста остаётся неподсвеченным, исправляем:

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {    super.onSizeChanged(w, h, oldw, oldh)    updateSyntaxHighlighting()}

Главное не забыть указать adjustResize в манифесте.

Скроллинг


Говоря про скроллинг снова буду ссылаться на эту статью. Автор предлагает ждать 500 мс после окончания скроллинга, что противоречит моему чувству прекрасного. Я не хочу дожидаться пока прогрузится подсветка, я хочу видеть результат моментально.

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

Достаточно вызывать метод отвечающий за обновление подсветки:

override fun onScrollChanged(horiz: Int, vert: Int, oldHoriz: Int, oldVert: Int) {    super.onScrollChanged(horiz, vert, oldHoriz, oldVert)    updateSyntaxHighlighting()}

Нумерация строк


Если мы добавим в разметку ещё один TextView то будет проблематично их между собой связать (например, синхронно обновлять размер текста), да и если у нас большой файл то придется полностью обновлять текст с номерами после каждой введенной буквы, что не очень круто. Поэтому будем использовать стандартные средства любой CustomView рисование на Canvas в onDraw, это и быстро, и не сложно.

Для начала определим что будем рисовать:

  • Номера строк
  • Вертикальную линию, отделяющую поле ввода от номеров строк

Предварительно необходимо вычислить и установить padding слева от редактора, чтобы не рисовать поверх написанного кода.

Для этого напишем функцию, которая будет обновлять отступ перед отрисовкой:

Обновление отступа
private var gutterWidth = 0private var gutterDigitCount = 0private var gutterMargin = 4.dpToPx() // отступ от разделителя в пикселях...private fun updateGutter() {    var count = 3    var widestNumber = 0    var widestWidth = 0f    gutterDigitCount = lineCount.toString().length    for (i in 0..9) {        val width = paint.measureText(i.toString())        if (width > widestWidth) {            widestNumber = i            widestWidth = width        }    }    if (gutterDigitCount >= count) {        count = gutterDigitCount    }    val builder = StringBuilder()    for (i in 0 until count) {        builder.append(widestNumber.toString())    }    gutterWidth = paint.measureText(builder.toString()).toInt()    gutterWidth += gutterMargin    if (paddingLeft != gutterWidth + gutterMargin) {        setPadding(gutterWidth + gutterMargin, gutterMargin, paddingRight, 0)    }}


Пояснение:

Для начала мы узнаем кол-во строк в EditText (не путать с кол-вом "\n" в тексте), и берем кол-во символов от этого числа. Например, если у нас 100 строк, то переменная gutterDigitCount будет равна 3, потому что в числе 100 ровно 3 символа. Но допустим, у нас всего 1 строка а значит отступ в 1 символ будет визуально казаться маленьким, и для этого мы используем переменную count, чтобы задать минимально отображаемый отступ в 3 символа, даже если у нас меньше 100 строк кода.

Эта часть была самая запутанная из всех, но если вдумчиво прочитать несколько раз (поглядывая на код), то всё станет понятно.

Далее вычисляем и устанавливаем отступ на основе имеющихся widestNumber и widestWidth.

Приступим к рисованию


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

setHorizontallyScrolling(true)

Ну а теперь можно приступать к рисованию, объявим переменные с типом Paint:

private val gutterTextPaint = Paint() // Нумерация строкprivate val gutterDividerPaint = Paint() // Отделяющая линия

Где-нибудь в init блоке установим цвет текста и цвет разделителя. Важно помнить, что если вы поменяйте шрифт текста, то шрифт Paint'а придется применять вручную, для этого советую переопределить метод setTypeface. Аналогично и с размером текста.

После чего переопределяем метод onDraw:

override fun onDraw(canvas: Canvas?) {    updateGutter()    super.onDraw(canvas)    var topVisibleLine = getTopVisibleLine()    val bottomVisibleLine = getBottomVisibleLine()    val textRight = (gutterWidth - gutterMargin / 2) + scrollX    while (topVisibleLine <= bottomVisibleLine) {        canvas?.drawText(            (topVisibleLine + 1).toString(),            textRight.toFloat(),            (layout.getLineBaseline(topVisibleLine) + paddingTop).toFloat(),            gutterTextPaint        )        topVisibleLine++    }    canvas?.drawLine(        (gutterWidth + scrollX).toFloat(),        scrollY.toFloat(),        (gutterWidth + scrollX).toFloat(),        (scrollY + height).toFloat(),        gutterDividerPaint    )}

Смотрим на результат




Выглядит круто.

Что же мы сделали в методе onDraw? Перед вызовом super-метода мы обновили отступ, после чего отрисовали номера только в видимой области, ну и под конец провели вертикальную линию, визуально отделяющую нумерацию строк от редактора кода.

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

Заключение


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

Также оставлю ссылку на исходники моего редактора кода на GitHub, там вы найдёте не только те фичи о которых я рассказал в этой статье, но и много других которые остались без внимания.

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

Спасибо!
Подробнее..

Редактор кода на Android часть 2

13.07.2020 20:06:08 | Автор: admin

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

Перед дальнейшим прочтением настоятельно рекомендую ознакомиться с первой частью.

Вступление


Для начала давайте вспомним на чем мы остановились в прошлой части. Мы написали оптимизированную подсветку синтаксиса, которая парсит текст в фоне и раскрашивает только его видимую часть, а так же добавили нумерацию строк (хоть и без андройдовских переносов на новую строку, но всё же).

В этой части мы добавим автодополнение кода и подсветку ошибок.

Автодополнение кода


Для начала представим как это должно работать:

  1. Пользователь пишет слово
  2. После ввода N первых символов появляется окошко с подсказками
  3. При нажатии на подсказку слово автоматически допечатывается
  4. Окошко с подсказками закрывается, и курсор переносится в конец слова
  5. Если пользователь ввел слово отображаемое в подсказке сам, то окошко с подсказками должно автоматически закрыться

Ничего не напоминает? В андройде уже есть компонент с точно такой же логикой MultiAutoCompleteTextView, поэтому писать костыли с PopupWindow нам не придется (их уже написали за нас).

Первым шагом поменяем родителя у нашего класса:

class TextProcessor @JvmOverloads constructor(    context: Context,    attrs: AttributeSet? = null,    defStyleAttr: Int = R.attr.autoCompleteTextViewStyle) : MultiAutoCompleteTextView(context, attrs, defStyleAttr)

Теперь нам нужно написать ArrayAdapter который будет отображать найденные результаты. Полного кода адаптера не будет, примеры реализации можно найти в интернете. Но на моменте с фильтрацией я всё таки остановлюсь.

Чтобы ArrayAdapter мог понимать какие подсказки нужно отобразить, нам нужно переопределить метод getFilter:

override fun getFilter(): Filter {    return object : Filter() {        private val suggestions = mutableListOf<String>()        override fun performFiltering(constraint: CharSequence?): FilterResults {            // ...        }        override fun publishResults(constraint: CharSequence?, results: FilterResults) {            clear() // необходимо очистить старый список            addAll(suggestions)            notifyDataSetChanged()        }    }}

И в методе performFiltering наполнить список suggestions из слов, основываясь на слове которое начал вводить пользователь (содержится в переменной constraint).

Откуда взять данные перед фильтрацией?


Тут всё зависит от вас можно использовать какой-нибудь интерпретатор для подбора только валидных вариантов, либо сканировать весь текст при открытии файла. Для простоты примера я буду использовать уже готовый список вариантов автодополнения:

private val staticSuggestions = mutableListOf(    "function",    "return",    "var",    "const",    "let",    "null"    ...)...override fun performFiltering(constraint: CharSequence?): FilterResults {    val filterResults = FilterResults()    val input = constraint.toString()    suggestions.clear() // очищаем старый список    for (suggestion in staticSuggestions) {        if (suggestion.startsWith(input, ignoreCase = true) &&             !suggestion.equals(input, ignoreCase = true)) {            suggestions.add(suggestion)        }    }    filterResults.values = suggestions    filterResults.count = suggestions.size    return filterResults}

Логика фильтрации тут довольно примитивная, проходимся по всему списку и игнорируя регистр сравниваем начало строки.

Установили адаптер, пишем текст не работает. Что не так? По первой ссылке в гугле натыкаемся на ответ, в котором говорится что мы забыли установить Tokenizer.

Для чего нужен Tokenizer?


Говоря простым языком, Tokenizer помогает MultiAutoCompleteTextView понять, после какого введенного символа можно считать ввод слова завершенным. Также у него есть готовая реализация в виде CommaTokenizer с разделением слов на запятые, что в данном случае нам не подходит.

Что ж, раз CommaTokenizer нас не устраивает, тогда напишем свой:

Кастомный Tokenizer
class SymbolsTokenizer : MultiAutoCompleteTextView.Tokenizer {    companion object {        private const val TOKEN = "!@#$%^&*()_+-={}|[]:;'<>/<.? \r\n\t"    }    override fun findTokenStart(text: CharSequence, cursor: Int): Int {        var i = cursor        while (i > 0 && !TOKEN.contains(text[i - 1])) {            i--        }        while (i < cursor && text[i] == ' ') {            i++        }        return i    }    override fun findTokenEnd(text: CharSequence, cursor: Int): Int {        var i = cursor        while (i < text.length) {            if (TOKEN.contains(text[i - 1])) {                return i            } else {                i++            }        }        return text.length    }    override fun terminateToken(text: CharSequence): CharSequence = text}


Разбираемся:
TOKEN строка с символами которые отделяют одно слово от другого. В методах findTokenStart и findTokenEnd мы проходимся по тексту в поисках этих самых отделяющих символов. Метод terminateToken позволяет вернуть измененный результат, но нам он не нужен, поэтому просто возвращаем текст без изменений.

Ещё я предпочитаю добавлять задержку на ввод в 2 символа перед отображением списка:

textProcessor.threshold = 2

Устанавливаем, запускаем, пишем текст работает! Вот только почему-то окошко с подсказками странно себя ведет отображается во всю ширину, высота у него маленькая, да и по идее оно ведь должно появляться под курсором, как будем фиксить?

Исправляем визуальные недостатки


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

Для начала определимся с размерами. На мой взгляд, наиболее удобным вариантом будет окошко размером с половину от высоты и ширины экрана, но т.к размер нашей View изменяется в зависимости от состояния клавиатуры, подбирать размеры будем в методе onSizeChanged:

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {    super.onSizeChanged(w, h, oldw, oldh)    updateSyntaxHighlighting()    dropDownWidth = w * 1 / 2    dropDownHeight = h * 1 / 2}

Выглядеть стало лучше, но не сильно. Мы хотим добиться чтобы окошко появлялось под курсором и перемещалось вместе с ним во время редактирования.

Если с перемещением по X всё довольно просто берем координату начала буквы и устанавливаем это значение в dropDownHorizontalOffset, то с подбором высоты будет сложнее.

Гугля про свойства шрифтов можно наткнуться на вот такой пост. Картинка которую прикрепил автор наглядно показывает, какими свойствами мы можем воспользоваться для вычисления вертикальной координаты.

Судя по картинке, Baseline это то что нам нужно. Именно на этом уровне и должно появляться окошко с вариантами автодополнения.

Теперь напишем функцию, которую будем вызывать при изменении текста в onTextChanged:

private fun onPopupChangePosition() {    val line = layout.getLineForOffset(selectionStart) // строка с курсором    val x = layout.getPrimaryHorizontal(selectionStart) // координата курсора    val y = layout.getLineBaseline(line) // тот самый baseline    val offsetHorizontal = x + gutterWidth // нумерация строк тоже часть отступа    dropDownHorizontalOffset = offsetHorizontal.toInt()    val offsetVertical = y - scrollY // -scrollY чтобы не "заезжать" за экран    dropDownVerticalOffset = offsetVertical}

Вроде ничего не забыли смещение по X работает, но смещение по Y рассчитывается неправильно. Это потому что мы не указали dropDownAnchor в разметке:

android:dropDownAnchor="@id/toolbar"

Указав Toolbar в качестве dropDownAnchor мы даём виджету понять, что выпадающий список будет отображаться под ним.

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

val offset = offsetVertical + dropDownHeightif (offset < getVisibleHeight()) {    dropDownVerticalOffset = offsetVertical} else {    dropDownVerticalOffset = offsetVertical - dropDownHeight}...private fun getVisibleHeight(): Int {    val rect = Rect()    getWindowVisibleDisplayFrame(rect)    return rect.bottom - rect.top}

Нам не нужно изменять отступ если сумма offsetVertical + dropDownHeight меньше видимой высоты экрана, ведь в таком случае окошко помещается под курсором. Но если всё таки больше, то вычитаем из отступа dropDownHeight так оно поместится над курсором без огромного отступа который добавляет сам виджет.

P.S На гифке можно заметить промаргивания клавиатуры, и честно говоря я не знаю как это исправить, поэтому если у вас есть решение пишите.

Подсветка ошибок


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

Как парсить будем?


Запуск Rhino довольно тяжелая операция, поэтому запускать парсер после каждого введенного символа (как мы делали с подсветкой) вообще не вариант. Для решения этой проблемы я буду использовать библиотеку RxBinding, а для тех кто не хочет тащить в проект RxJava можно попробовать подобные варианты.

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

textProcessor.textChangeEvents()    .skipInitialValue()    .debounce(1500, TimeUnit.MILLISECONDS)    .filter { it.text.isNotEmpty() }    .distinctUntilChanged()    .observeOn(AndroidSchedulers.mainThread())    .subscribeBy {        // Запуск парсера будет тут    }    .disposeOnFragmentDestroyView()

Теперь напишем модель которую нам будет возвращать парсер:

data class ParseResult(val exception: RhinoException?)

Предлагаю использовать такую логику: если ошибок не найдено, то exception будет null. В противном случае мы получим объект RhinoException который содержит в себе всю необходимую информацию номер строки, сообщение об ошибке, StackTrace и т.д.

Ну и собственно, сам парсинг:

// Это должно выполняться в фоне !val context = Context.enter() // org.mozilla.javascript.Contextcontext.optimizationLevel = -1context.maximumInterpreterStackDepth = 1try {    val scope = context.initStandardObjects()    context.evaluateString(scope, sourceCode, fileName, 1, null)    return ParseResult(null)} catch (e: RhinoException) {    return ParseResult(e)} finally {    Context.exit()}

Разбираемся:
Самое главное тут это метод evaluateString он позволяет запустить код, который мы передали в качестве строки sourceCode. В fileName указывается имя файла оно будет отображаться в ошибках, единица номер строки для начала отсчета, последний аргумент это security domain, но он нам не нужен, поэтому ставим null.

optimizationLevel и maximumInterpreterStackDepth


Параметр optimizationLevel со значением от 1 до 9 позволяет включить определенные оптимизации кода (data flow analysis, type flow analysis и т.д), что превратит простую проверку синтаксических ошибок в очень длительную операцию, а нам это не к чему.

Если же использовать его со значением 0, то все эти оптимизации применяться не будут, однако, если я правильно понял, Rhino по-прежнему будет использовать часть ресурсов не нужных для простой проверки ошибок, а значит нам это не подходит.

Остаётся только отрицательное значение указав -1 мы активируем режим интерпретатора, а это именно то что нам нужно. В документации сказано что это самый быстрый и экономичный вариант работы Rhino.

Параметр maximumInterpreterStackDepth позволяет ограничить количество рекурсивных вызовов.

Представим что будет если не указать этот параметр:

  1. Пользователь напишет следующий код:

    function recurse() {    recurse();}recurse();
    
  2. Rhino запустит код, и через секунду наше приложение вылетит с OutOfMemoryError. Конец.

Отображение ошибок


Как я говорил ранее, как только мы получим ParseResult содержащий RhinoException, у нас появится весь необходимый набор данных для отображения, в том числе и номер строки нужно лишь вызвать метод lineNumber().

Теперь напишем спан с красной волнистой линией, который я скопировал на StackOverflow. Кода много, но логика простая рисуем две короткие красные линии под разным углом.

ErrorSpan.kt
class ErrorSpan(    private val lineWidth: Float = 1 * Resources.getSystem().displayMetrics.density + 0.5f,    private val waveSize: Float = 3 * Resources.getSystem().displayMetrics.density + 0.5f,    private val color: Int = Color.RED) : LineBackgroundSpan {    override fun drawBackground(        canvas: Canvas,        paint: Paint,        left: Int,        right: Int,        top: Int,        baseline: Int,        bottom: Int,        text: CharSequence,        start: Int,        end: Int,        lineNumber: Int    ) {        val width = paint.measureText(text, start, end)        val linePaint = Paint(paint)        linePaint.color = color        linePaint.strokeWidth = lineWidth        val doubleWaveSize = waveSize * 2        var i = left.toFloat()        while (i < left + width) {            canvas.drawLine(i, bottom.toFloat(), i + waveSize, bottom - waveSize, linePaint)            canvas.drawLine(i + waveSize, bottom - waveSize, i + doubleWaveSize, bottom.toFloat(), linePaint)            i += doubleWaveSize        }    }}


Теперь можно написать метод установки спана на проблемную строку:

fun setErrorLine(lineNumber: Int) {    if (lineNumber in 0 until lineCount) {        val lineStart = layout.getLineStart(lineNumber)        val lineEnd = layout.getLineEnd(lineNumber)        text.setSpan(ErrorSpan(), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)    }}

Важно помнить, что т.к результат приходит с задержкой, пользователь может успеть стереть пару строк кода, и тогда lineNumber может оказаться невалидным.

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

Главное не забыть очистить текст от уже установленных спанов в afterTextChanged:

fun clearErrorSpans() {    val spans = text.getSpans<ErrorSpan>(0, text.length)    for (span in spans) {        text.removeSpan(span)    }}

Почему редакторы кода лагают?


За две статьи мы написали неплохой редактор кода наследуясь от EditText и MultiAutoCompleteTextView, но производительностью при работе с большими файлами похвастаться не можем.

Если открыть тот же TextView.java на 9к+ строк кода то любой текстовый редактор написанный по такому же принципу как наш будет лагать.

Q: А почему QuickEdit тогда не лагает?
A: Потому что под капотом он не использует ни EditText, ни TextView.

В последнее время набирают популярность редакторы кода на CustomView (вот и вот, ну или вот и вот, их очень много). Исторически так сложилось, что TextView имеет слишком много лишней логики, которая не нужна редакторам кода. Первое что приходит на ум Autofill, Emoji, Compound Drawables, кликабельные ссылки и т.д.

Если я правильно понял, авторы библиотек просто избавились от всего этого, в следствие чего получили текстовый редактор способный работать с файлами в миллион строк без особой нагрузки на UI Thread. (Хотя частично могу ошибаться, в исходниках не сильно разобрался)

Есть ещё один вариант, но на мой взгляд менее привлекательный редакторы кода на WebView (вот и вот, их тоже очень много). Мне они не нравятся потому что UI на WebView выглядит хуже чем нативный, да и редакторам на CustomView они так же проигрывают по производительности.

Заключение


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

Также оставлю ссылку на исходники моего редактора кода на GitHub, там вы найдёте не только те фичи о которых я рассказал за эти две статьи, но и много других которые остались без внимания.

Спасибо!
Подробнее..

IntelliJ IDEA Structural Search amp Replace

10.07.2020 22:15:48 | Автор: admin


Современные IDE очень мощные инструменты, способные помогать разработчику в самых разных ситуациях. Но обратной стороной этой мощности является то, что большинство функций находится в тени об их наличии мало кто подозревает.


Простой пример одной такой функции

А вы знаете, что, если в IDEA нажать F2, курсор перескочит к ближайшей ошибке в файле? А если нет ошибки, то к замечанию? Как-то так получается, что об этом знают далеко не все.


Одной такой функцией является Structural Search & Replace (SSR). Она может быть невероятно полезна в тех ситуациях, когда пасует всё богатое разнообразие других функций.


Несколько таких примеров я и приведу в данной статье. И чтобы не ограничиваться суррогатными случаями, буду демонстрировать эти примеры на реальном коде из двух проектов:


  1. 3D-движка для создания игр jMonkeyEngine, как пример большого проекта, в котором всегда можно найти что-то интересненькое.
  2. моего собственного проекта plantuml-native-image, в котором я провожу эксперименты по компиляции PlantUML в нативный исполняемый код с помощью GraalVM native-image.

Собственно, случай во втором проекте и побудил меня к написанию статьи. Но обо всём по порядку...


Простая задача для препарирования


Прежде чем приступить к изучению структурного поиска как такового, давайте определимся с какой-либо простой задачей, в решении которой этот поиск будет полезен. Тут я хочу рассмотреть пример, который не так давно пригодился мне лично в одном из рабочих проектов (с тем лишь исключением, что вместо закрытого рабочего кода я буду демонстрировать пример на конкретной ревизии проекта jMonkeyEngine): поиск открытых объектов блокировок с использованием ключевого слова synchronized (см. раздел "Item 82 Document thread safety" главы "11 Concurrency" в книге Joshua Bloch "Effective Java").


Если кратко, суть в том, что использование синхронизации на публично доступных объектах является не очень хорошей идеей, т.к. тогда теряется контроль над синхронизацией, и любой сторонний код может начать в неё вмешиваться, приводя к нежелательным эффектам различного рода, вплоть до взаимоблокировок (deal locks).


Тут надо понимать, что ключевое слово synchronized имеет два варианта использования:


в качестве модификатора метода:


class ClassA {    public synchronized void someMethod() {        // ...    }}

и в качестве внутренней конструкции метода:


class ClassA {    public void someMethod() {        synchronized(this) {            // ...        }    }}

По сути, оба приведённых выше примера являются примерами синхронизации на открытом объекте. Правильным было бы написание такого кода:


class ClassA {    private final Object sync = new Object();    public void someMethod() {        synchronized(sync) {            // ...        }    }}

В таком примере никакой сторонний код не сможет вмешаться в синхронизацию.


Но как понять, есть ли в коде проекта такой паттерн?


Простейший подход поиск по тексту ключевого слова synchronized и внимательный разбор каждого вхождения. Но такой подход годится только для совсем маленьких проектов. Если мы попытаемся поискать такое слово в проекте jMonkeyEngine, то найдём там аж 117 вхождений, причём не только в качестве искомой конструкции, но и в комментариях и просто строках. И разбираться в таком количестве вхождений уже далеко не так весело.


Так что же делать? Вот тут нам на помощь и приходит уникальная функция IntelliJ IDEA: Structural Search & Replace.


Structural Search. Основы


Для начала разберёмся с интерфейсом структурного поиска в IDEA.


Откроем проект jMonkeyEngine, и вызовем окно структурного поиска (Edit -> Find -> Search Structurally...) с двумя областями ввода:




  1. областью задания шаблона поиска;
  2. областью задания фильтров.

Чем же оно отличается от обычного поиска, и что мы можем с этим делать?


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


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




В результате откроется окно с богатым выбором готовых шаблонов:




В этом каталоге очень много примеров, полезных как для простого изучения, так и для применения на практике. Но мы определились, что хотим искать точки синхронизации.


Начнём со случая, когда ключевое слово synchronized используется в качестве модификатора метода, т.к. в этом случае синхронизация получается по открытому методу просто by design. Для этого введём в левое поле следующую странную конструкцию:


synchronized $type$ $method$ ($ptype$ $param$) {    $statement$;}

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


В этом шаблоне можно увидеть большое количество лексем, ограниченных спереди и сзади символами $. Это так называемые переменные шаблона поиска. Если лексема задана без символов $ (как, например synchronized), то она должна присутствовать в найденном фрагменте кода, как есть. А если лексема находится внутри "долларов", то в этом месте по умолчанию может находиться что угодно. Просто тут должно находиться что-то, не важно, что.


Имена переменных внутри "долларов" могут быть произвольными. По ним в дальнейшем может производиться настройка дополнительных критериев в правой области окна, если это необходимо.


Т.о., если мы взглянем на шаблон абстрактно, то можем понять, что тут производится поиск определения произвольного метода, с каким-то возвращаемым типом (включая void), с каким-то параметром и строкой в теле метода. А главное, этот метод является синхронизируемым.


Но погодите, мы же хотим найти все синхронизируемые методы, с произвольным количеством параметров (включая методы без параметров) и с произвольно сложным телом. Как быть?


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


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


Т.е. если мы слева выделим переменную $param$, то справа можно задать для неё, например, критерий количества вхождений параметров в объявлении функции. Для этого достаточно щёлкнуть на гиперссылку Add Filter и выбрать второй пункт меню Count.




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




Теперь шаблон будет искать все синхронизируемые методы с произвольным количеством параметров Но только с одной строкой кода в теле. Чтобы это исправить, зададим точно такое же ограничение и для переменной $statement$.


Всё, теперь можно нажать кнопку Find и увидеть все синхронизируемые методы в проекте, которых всего 18 штук:




Отличное начало!


Спускаемся в кроличью нору. Script Filter


Ну а что с синхронизацией на произвольном объекте? Казалось бы, тут всё даже проще:


synchronized($Obj$) {    $statement$;}

Но вот беда, как определить, что переменная $Obj$ приватная? Можно попробовать отталкиваться от паттерна из книги, что это должна быть переменная типа Object. Тогда мы можем добавить фильтр Type и задать имя типа класса Object. Причём соответствие должно быть строгим, без учёта иерархии. Т.е. галки под фильтром должны быть убраны:




Тогда мы с некоторой вероятностью найдём все места, где синхронизация использует закрытые объекты (да и то не все!). Что как бы даст нам результат, противоположный требуемому В частных случаях это может быть полезно, если нам надо найти все синхронизации по объектам определённого типа и даже по наследникам этого типа (если отметить пункт with type hierarchy под фильтром).


Но в целом-таки хотелось бы найти именно те случаи, где используется не приватный объект. Любого типа.


К сожалению, простых способов найти это не существует. Единственное, что нам может помочь ограничение типа Script. Самая мощная и самая сложно используемая функция структурного поиска.


Дело в том, что в качестве ограничения можно написать произвольный код на Groovy, возвращающий true или false. А в качестве параметров будут переменные из паттерна поиска, которые определены в символах $. Плюс ещё пара служебных переменных __context__ и __log__.


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


  • поле ввода скрипта (внезапно) не предоставляет никакого code IntelliSense. Никаких подсказок;
  • угадать какой именно элемент Psi дерева окажется в качестве переменной практически нереально;
  • никакой справки по структуре Psi дерева. Только исходники;
  • общее юзабилити ввода фильтра ужасно. Необходимо смириться с его схлопываниями при потере фокуса ввода и необходимостью каждый раз расхлопывать поле и изменять размеры элементов, чтобы увидеть скрипт полностью, если он большой и многострочный.

Так что же делать? Будем разбираться. Прежде всего надо понять что же попадает в переменную $Obj$. Мне не пришло в голову ничего лучше идеи воспользоваться штатной функцией из Groovy: println. Добавляем к переменной $Obj$ фильтр типа Script и вводим следующий текст:


println(Obj)return true



Как видим, в скрипте можно использовать имена переменных, только без "долларов". При выполнении этого поиска мы увидим все вхождения кода с synchronized, но нас интересует не это, а то, куда же напечатались логи с помощью println?


А напечатались они в лог самой IDEA. Найти их можно через меню: Help->Show Log in.... Т.к. у меня KDE, то пункт меню полностью называется Show Log in Dolphin. В результате откроется системный диспетчер файлов с указанием на актуальный файл лога. Вот в него-то и нужно заглянуть, чтобы увидеть информацию об интересующем нас объекте. В данном случае можно найти следующие строки:


2020-07-05 15:03:00,998 [14151177] INFO - STDOUT - PsiReferenceExpression:pending2020-07-05 15:03:01,199 [14151378] INFO - STDOUT - PsiReferenceExpression:source2020-07-05 15:03:01,216 [14151395] INFO - STDOUT - PsiThisExpression:this2020-07-05 15:03:01,219 [14151398] INFO - STDOUT - PsiReferenceExpression:receiveObjectLock2020-07-05 15:03:01,222 [14151401] INFO - STDOUT - PsiReferenceExpression:invoke2020-07-05 15:03:01,226 [14151405] INFO - STDOUT - PsiReferenceExpression:chatServer2020-07-05 15:03:01,231 [14151410] INFO - STDOUT - PsiReferenceExpression:obj2020-07-05 15:03:01,236 [14151415] INFO - STDOUT - PsiReferenceExpression:sync2020-07-05 15:03:01,242 [14151421] INFO - STDOUT - PsiReferenceExpression:image2020-07-05 15:03:01,377 [14151556] INFO - STDOUT - PsiClassObjectAccessExpression:TerrainExecutorService.class2020-07-05 15:03:01,409 [14151588] INFO - STDOUT - PsiReferenceExpression:byteBuffer2020-07-05 15:03:01,429 [14151608] INFO - STDOUT - PsiReferenceExpression:lock2020-07-05 15:03:01,432 [14151611] INFO - STDOUT - PsiReferenceExpression:eventQueue2020-07-05 15:03:01,456 [14151635] INFO - STDOUT - PsiReferenceExpression:sensorData.valuesLock2020-07-05 15:03:01,593 [14151772] INFO - STDOUT - PsiReferenceExpression:createdLock2020-07-05 15:03:01,614 [14151793] INFO - STDOUT - PsiReferenceExpression:taskLock2020-07-05 15:03:01,757 [14151936] INFO - STDOUT - PsiReferenceExpression:loaders2020-07-05 15:03:01,765 [14151944] INFO - STDOUT - PsiReferenceExpression:threadLock

Т.о., мы видим, что в качестве значения Obj могут выступать объекты как минимум трёх видов:


  • PsiThisExpression лексема this;
  • PsiClassObjectAccessExpression синхронизация по объекту типа Class (synchronized (TerrainExecutorService.class) {...});
  • PsiReferenceExpression некоторое выражение, результат вычисления которого используется как объект синхронизации.

Первые два типа можно автоматически рассматривать как синхронизацию по открытому объекту. Т.е. если у нас Obj является объектом типа PsiThisExpression или PsiClassObjectAccessExpression, то надо вернуть true.


Но что делать с типом PsiReferenceExpression? Точнее, даже не так. Что с ним вообще можно сделать?


К сожалению, единственный способ поиска ответа на этот вопрос, найденный мной обратиться к исходникам. Т.к. Java парсер от JetBrains является открытым и лежит на GitHub в составе исходников IDEA, то ничто не мешает заглянуть в него. Интересующий нас класс находится тут.


Не буду утомлять подробностями ковыряния исходников Psi. Просто приведу получившийся результирующий скрипт:


if (Obj instanceof com.intellij.psi.PsiThisExpression) return trueif (Obj instanceof com.intellij.psi.PsiClassObjectAccessExpression) return trueif (Obj instanceof com.intellij.psi.PsiReferenceExpression) {    def var = Obj.advancedResolve(false).element    if (var instanceof com.intellij.psi.PsiParameter) return true    if (var instanceof com.intellij.psi.PsiLocalVariable) {        return !(var.initializer instanceof com.intellij.psi.PsiNewExpression)    }    if (var instanceof com.intellij.psi.PsiField) {        return !var.hasModifier(com.intellij.lang.jvm.JvmModifier.PRIVATE) &&               !var.hasModifier(com.intellij.lang.jvm.JvmModifier.PROTECTED)    }}return true

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


Ваша первая в жизни статическая инспекция кода


Как мы увидели чуть ранее, поиск некоторых вещей может оказаться довольно непростой задачей. И будет обидно, если столько усилий пропадёт зря. Да и вообще, хотелось бы, чтобы IDE сразу подсказывала, что ты пишешь что-то не то.


И тут IDEA тоже может помочь. Дело в том, что в ней есть огромное количество всяких инспекций, которые помогают разработчику писать более правильный код, подсвечивая некорректные места и объясняя, что в них не так. И среди них есть одна замечательная инспекция, которая выключена по умолчанию. Это: Structural search inspection.


Найти эту инспекцию можно в окне настройки (File->Settings...->Editor->Inspections):




Включаем эту инспекцию и нажимаем плюсик, чтобы добавить шаблон структурного поиска. По умолчанию окно поиска автоматически заполнится тем, что искалось в последний раз. Нажимаем Ok и вводим имя шаблона поиска. Например, Open object sync. Но на деле лучше придумать что-то более развёрнутое, чтобы было понятно, что тут имеется в виду.


Всё, теперь IDEA начнёт автоматически подсвечивать все места в коде, которые подпадают под этот поиск:




Ву-а-ля!!! Вы создали первую в своей жизни инспекцию кода! Поздравляю! Остаётся только закоммитить её вместе с проектом, чтобы другие тоже могли ею воспользоваться. Для этого достаточно добавить в версионное хранилище каталог .idea/inspectionProfiles, где сохранились эти настройки.


Structural Replace


Как и в Structural Search, в IDEA есть и функция структурной замены Structural Replace
(Edit -> Find -> Replace Structurally...):




В отличие от окна Search Structurally, в данном окне появляется ещё одна область. Она предназначена для задания замены найденному шаблону. Так же, как и в шаблоне, в подстановке могут содержаться переменные, ограниченные с двух сторон символами $. И точно так же эти переменные могут настраиваться в области ввода фильтров. Только в этом случае это будут не условия фильтрации, а исключительно Groovy скрипты для вычисления текста подстановки вместо переменной.


Тут тоже хочу привести пример из жизни. Мне надо было в одном огромном классе заменить все вызовы функций вида:


classInitializationSupport.initializeAtRunTime(WindowPropertyGetter.class, AWT_SUPPORT);

на вызовы вида:


classInitializationSupport.initializeAtRunTime("sun.awt.X11.WindowPropertyGetter", AWT_SUPPORT);

Т.е. надо было уйти от явного использования классов в коде. И далее сделать то же самое в отношении нескольких видов функций.


Проделывать всё это вручную мне очень не хотелось. Вместо этого я использовал Replace Structurally. Для начала задал шаблон поиска:


classInitializationSupport.initializeAtRunTime($Clazz$.class, AWT_SUPPORT)

На переменную $Class$ не навешивал никаких ограничений, т.к. мне всё равно какой класс туда попадёт.


Далее задал подстановку:


classInitializationSupport.initializeAtRunTime("$FullClass$", AWT_SUPPORT)

И вот тут-то мне надо вычислить новое значение переменной $FullClass$. Для чего выделяю её курсором и в области ввода ограничений задаю скрипт вида:


Clazz.type.canonicalText

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


В конечном итоге окно структурной замены получило следующий вид:




Далее нажимаем Find и получаем список возможных замен:




Тут мы можем посмотреть каждое вхождение, и во что оно превратится (кнопка Preview Replacement). Так же можно исключить какие-либо вхождения (из контекстного меню) или сделать все преобразования разом (кнопка Replace All).


В целом, если освоиться с Psi, то это уже не так и сложно, не правда ли?


Structural Replace as Intention


Тут пришла пора упомянуть ещё один мощнейший механизм IDEA Intentions. Интеншены позволяют ковать железо, не отходя от кассы, т.е. применять какие-либо преобразования кода прямо по месту нахождения курсора.


Например, если вы напишете такой код:


public static void main(String[] args) {    List<Integer> list = Arrays.asList(0, 10, 20, 30);    for (int i = 0; i < list.size(); i++) {        System.out.println(list.get(i));    }}

а потом встанете курсором на for и нажмёте на Alt+Enter, то вам будет выдано предложение переделать этот цикл в несколько вариантов другого представления того же самого. Например, в for-each:


public static void main(String[] args) {    List<Integer> list = Arrays.asList(0, 10, 20, 30);    for (Integer integer : list) {        System.out.println(integer);    }}

Так вот, к чему это я? А к тому, что с помощью Structural Replace можно сделать такой же intention. Для этого достаточно дополнить ту же самую инспекцию, которую мы задавали для Structural Search. Только при добавлении нового пункта нужно указать, что это будет не поиск, а замена:






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






Поздравляю ещё раз! Вы сделали первый в своей жизни Intention!


Заключение


В данной статье я постарался познакомить читателей с одной из самых сложных для понимания, но при этом и одной из самых мощных функций в IntelliJ IDEA. Да, не всё в ней сейчас удобно, тем не менее бывают ситуации, когда ни одно другое средство не справится.


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


Дерзайте. Удачи. И всем Java!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru