diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index d84f269..95acb18 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -1,54 +1,269 @@ -name: Gitea Actions Demo -run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 -on: [push] +name: Build and Release +on: + push: + branches: + - master + - main + workflow_dispatch: # Ручной запуск jobs: - Explore-Gitea-Actions: + build: runs-on: tcl-tk-builder steps: - - name: Build the DEB-packages + - name: Клонирование run: | - git clone ${{ vars.main_url }}${{ gitea.repository }} - pwd - cd projman/debian - ./build-deb-projman.sh - cd ../openbsd - ./build-package-bsd.sh - cd ../../ - curl --user ${{secrets.USER}}:${{secrets.API_TOKEN}} --upload-file "$(ls -1| grep projman | grep -E 'deb$')" ${{vars.main_url}}api/packages/${{vars.user}}/debian/pool/bookworm/main/upload - - run: echo "This job's status is ${{ job.status }}." - - - name: Create release - run: | - # Создаем релиз через API - curl -X POST \ - -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ - -H "Content-Type: application/json" \ - -d '{ - "tag_name": "'"${{ gitea.ref_name }}"'", - "name": "Release '"${{ gitea.ref_name }}"'", - "body": "Automated release for '"${{ gitea.ref_name }}"'", - "draft": false, - "prerelease": false - }' \ - "${{vars.main_url}}/api/v1/repos/${{ gitea.repository }}/releases" - - - name: Get release ID - id: release_info - run: | - response=$(curl -s -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ - "${{vars.main_url}}/api/v1/repos/${{ gitea.repository }}/releases/tags/${{ gitea.ref_name }}") + git clone "${{ vars.main_url }}${{ gitea.repository }}.git" . - echo "id=$(echo $response | jq -r '.id')" >> $GITHUB_OUTPUT - - - name: Upload Linux package + - name: Получение версии + id: get_version run: | - curl --user "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ - --upload-file "$(ls -1| grep projman | grep -E 'deb$')" \ - "${{vars.main_url}}/api/v1/repos/${{ gitea.repository }}/releases/${{ steps.release_info.outputs.id }}/assets?name=$(ls -1| grep projman | grep -E 'deb$')" + VERSION=$(grep "Version" projman.tcl | head -1 | grep -o '[0-9.]\+[a-zA-Z0-9]*' || echo "1.0.0") + RELEASE=$(grep "# Release" projman.tcl | tail -1 | awk '{print $NF}' || echo "$(date +%Y%m%d)") + + # Создаем имя тега + TAG="v${VERSION}" + echo "TAG=$TAG" >> $GITEA_ENV + echo "VERSION=$VERSION" >> $GITEA_ENV + echo "RELEASE=$RELEASE" >> $GITEA_ENV + + echo "Тег: $TAG" + echo "Версия: $VERSION" + echo "Ревизия: $RELEASE" - - name: Upload OpenBSD package + - name: Проверка существования тега + id: check_tag run: | - curl --user "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ - --upload-file "$(ls -1| grep projman | grep -E 'tgz$')" \ - "${{vars.main_url}}/api/v1/repos/${{ gitea.repository }}/releases/${{ steps.release_info.outputs.id }}/assets?name=$(ls -1| grep projman | grep -E 'tgz$')" + echo "Проверяем тег: $TAG" + + # Проверяем на удаленном сервере + if git ls-remote --tags origin "$TAG" 2>/dev/null | grep -q "$TAG"; then + echo "Тег $TAG уже существует на удаленном сервере" + echo "TAG_EXISTS_REMOTE=true" >> $GITEA_ENV + else + echo "Тег $TAG не существует на удаленном сервере" + echo "TAG_EXISTS_REMOTE=false" >> $GITEA_ENV + fi + + - name: Создание тега (только если не существует) + if: env.TAG_EXISTS_REMOTE == 'false' + run: | + echo "Создаем новый тег: $TAG" + git config user.email "svk@nuk-svk.ru" + git config user.name "svk" + + # Создаем тег локально + git tag -a "$TAG" -m "Release $TAG - $RELEASE" + + # Настраиваем URL для push + git remote set-url origin "https://${{ secrets.USER }}:${{ secrets.API_TOKEN }}@git.nuk-svk.ru/${{ gitea.repository }}.git" + + # Пушим тег на сервер + git push origin "$TAG" + + - name: Сборка пакетов + run: | + echo "=== Сборка DEB пакета ===" + cd debian && ./build-deb-projman.sh + + echo "=== Сборка OpenBSD пакета ===" + cd ../openbsd && ./build-package-bsd.sh + + echo "=== Собранные файлы ===" + find . -maxdepth 1 -name "projman*" -type f | xargs ls -la 2>/dev/null || echo "Файлы не найдены" + + - name: Проверка существования релиза + id: check_release + run: | + # Проверяем, существует ли уже релиз для этого тега + RESPONSE=$(curl -s -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ + "${{ vars.main_url }}api/v1/repos/${{ gitea.repository }}/releases/tags/$TAG" || echo "{}") + + echo "Ответ API проверки релиза: $RESPONSE" + + # Извлекаем id релиза - первый id в JSON + if echo "$RESPONSE" | grep -q '"id"'; then + # Извлекаем только первый id (id релиза), игнорируем id автора + # Используем awk для точного извлечения + REL_ID=$(echo "$RESPONSE" | awk -F'"id":' '{print $2}' | awk -F',' '{print $1}' | head -1 | tr -d ' ') + echo "Релиз уже существует. ID: $REL_ID" + echo "RELEASE_EXISTS=true" >> $GITEA_ENV + + # Очищаем и сохраняем REL_ID в файл + echo -n "$REL_ID" | tr -d '\n' > /tmp/rel_id.txt + else + echo "Релиз не существует" + echo "RELEASE_EXISTS=false" >> $GITEA_ENV + echo -n "" > /tmp/rel_id.txt + fi + + - name: Создание или обновление релиза + id: create_release + run: | + # Читаем REL_ID из файла и очищаем от лишних символов + REL_ID=$(cat /tmp/rel_id.txt 2>/dev/null | tr -d '\n\r ' || echo "") + + echo "=== Работа с релизом для тега $TAG ===" + echo "RELEASE_EXISTS: $RELEASE_EXISTS" + echo "REL_ID: '$REL_ID'" + + RELEASE_BODY="## Projman $VERSION + + **Ревизия:** $RELEASE + **Дата сборки:** $(date) + **Коммит:** $(git rev-parse --short HEAD)" + + + # Экранируем переносы строк для JSON + ESCAPED_BODY=$(echo "$RELEASE_BODY" | sed ':a;N;$!ba;s/\n/\\n/g') + + if [ "$RELEASE_EXISTS" = "true" ] && [ -n "$REL_ID" ]; then + echo "Обновляем существующий релиз ID: $REL_ID" + + # Обновляем существующий релиз + RESPONSE=$(curl -s -X PATCH \ + -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Projman '"$VERSION"'", + "body": "'"$ESCAPED_BODY"'", + "draft": false, + "prerelease": false + }' \ + "${{ vars.main_url }}api/v1/repos/${{ gitea.repository }}/releases/$REL_ID") + + echo "Ответ обновления релиза: $RESPONSE" + + # Проверяем ответ + if echo "$RESPONSE" | grep -q '"id"'; then + echo "Релиз успешно обновлен" + else + echo "ОШИБКА: Не удалось обновить релиз" + echo "Ответ: $RESPONSE" + exit 1 + fi + + else + echo "Создаем новый релиз" + + # Создаем новый релиз + RESPONSE=$(curl -s -X POST \ + -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d '{ + "tag_name": "'"$TAG"'", + "name": "Projman '"$VERSION"'", + "body": "'"$ESCAPED_BODY"'", + "draft": false, + "prerelease": false + }' \ + "${{ vars.main_url }}api/v1/repos/${{ gitea.repository }}/releases") + + echo "Ответ создания релиза: $RESPONSE" + + # Получаем ID нового релиза + NEW_REL_ID=$(echo "$RESPONSE" | awk -F'"id":' '{print $2}' | awk -F',' '{print $1}' | head -1 | tr -d ' ') + if [ -n "$NEW_REL_ID" ]; then + echo "Новый ID релиза: $NEW_REL_ID" + echo -n "$NEW_REL_ID" > /tmp/rel_id.txt + else + echo "ОШИБКА: Не удалось получить ID релиза из ответа" + echo "Ответ: $RESPONSE" + echo -n "" > /tmp/rel_id.txt + exit 1 + fi + fi + + - name: Загрузка файлов в релиз + run: | + # Читаем REL_ID из файла и очищаем + REL_ID=$(cat /tmp/rel_id.txt 2>/dev/null | tr -d '\n\r ' || echo "") + + if [ -z "$REL_ID" ]; then + echo "Нет ID релиза, пропускаем загрузку файлов" + exit 0 + fi + + echo "=== Загрузка файлов в релиз ===" + echo "ID релиза для загрузки: $REL_ID" + + # Находим все файлы projman + FILES=$(find ../ -maxdepth 1 \( -name "*projman*deb" -o -name "*projman*tgz" \) -type f) + if [ -z "$FILES" ]; then + echo "Нет файлов projman для загрузки" + exit 0 + fi + + echo "Найдены файлы:" + echo "$FILES" + + # Сначала проверим существующие ассеты + echo "=== Проверяем существующие ассеты ===" + curl -s -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ + "${{ vars.main_url }}api/v1/repos/${{ gitea.repository }}/releases/$REL_ID/assets" | \ + jq -r '.[].name' 2>/dev/null || echo "Не удалось получить список ассетов" + + # Загружаем каждый файл + for FILE in $FILES; do + FILENAME=$(basename "$FILE") + echo "Загружаем: $FILENAME" + + # Используем правильный endpoint для загрузки ассетов + UPLOAD_URL="${{ vars.main_url }}api/v1/repos/${{ gitea.repository }}/releases/$REL_ID/assets" + + echo "URL загрузки: $UPLOAD_URL?name=$FILENAME" + + # Загружаем файл + RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \ + --user "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ + -H "Content-Type: application/octet-stream" \ + -X POST \ + --data-binary @"$FILE" \ + "$UPLOAD_URL?name=$FILENAME") + + HTTP_STATUS=$(echo "$RESPONSE" | grep "HTTP_STATUS:" | cut -d':' -f2) + API_RESPONSE=$(echo "$RESPONSE" | grep -v "HTTP_STATUS:") + + echo "Статус: $HTTP_STATUS" + echo "Ответ API: $API_RESPONSE" + + if [ "$HTTP_STATUS" = "201" ] || [ "$HTTP_STATUS" = "200" ]; then + echo "✅ Файл загружен: $FILENAME" + else + echo "❌ ОШИБКА загрузки: $FILENAME" + echo "Детали: $API_RESPONSE" + fi + + echo "---" + done + + # Проверяем итоговый список ассетов + echo "=== Итоговый список ассетов ===" + curl -s -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ + "${{ vars.main_url }}api/v1/repos/${{ gitea.repository }}/releases/$REL_ID/assets" | \ + jq -r '.[] | "\(.name) - \(.browser_download_url)"' 2>/dev/null || \ + curl -s -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ + "${{ vars.main_url }}api/v1/repos/${{ gitea.repository }}/releases/$REL_ID/assets" | \ + grep -o '"name":"[^"]*"' | cut -d'"' -f4 + + - name: Финализация + run: | + # Читаем REL_ID из файла + REL_ID=$(cat /tmp/rel_id.txt 2>/dev/null | tr -d '\n\r ' || echo "") + + echo "=== Сборка завершена ===" + echo "Тег: $TAG" + echo "Версия: $VERSION" + echo "Ревизия: $RELEASE" + echo "ID релиза: $REL_ID" + + if [ -n "$REL_ID" ]; then + echo "Проверяем файлы в релизе..." + curl -s -u "${{ secrets.USER }}:${{ secrets.API_TOKEN }}" \ + "${{ vars.main_url }}api/v1/repos/${{ gitea.repository }}/releases/$REL_ID/assets" | \ + grep -o '"name":"[^"]*"' | cut -d'"' -f4 || echo "Не удалось получить список файлов" + else + echo "Релиз не был создан или ID не получен" + fi + + echo "Собранные файлы:" + find ../ -maxdepth 1 \( -name "*projman*deb" -o -name "*projman*tgz" \) -type f | xargs ls -la 2>/dev/null || echo "Файлы не найдены" diff --git a/README.md b/README.md index bacf2bc..62c1e53 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,29 @@ Or type "projman" into terminal, Or choose the name of the program "Projman" on - Alt-S - Split the edited window horizontally - Alt-K - Open folder +### Work with external tools +ProjMan allows you to connect any external tools to the editor. To do this, you need to add an entry to the file ~/.config/projman/tools.ini. + +Calling an external program is available through the main and pop-up menus. To transfer the parameters, write the appropriate template in the file. + - %s - template for substituting selected text in the editor + - %f - template for substituting selected file\(s\) in the file tree + +When adding multiple %f templates, the corresponding number of files allocated in the tree will be substituted. + +``` +[TkDIFF] +commandString=tkdiff %f %f +description=TkDiff is a Tcl/Tk front-end to diff +icon= +shortCut= + +[VisualRegexp] +commandString=tkregexp "%s" +description=A graphical front-end to write/debug regular expression +icon= +shortCut= +``` + ## Credits Sergey Kalinin - author diff --git a/debian/build-deb-projman.sh b/debian/build-deb-projman.sh index d332154..0cb4911 100755 --- a/debian/build-deb-projman.sh +++ b/debian/build-deb-projman.sh @@ -11,6 +11,7 @@ sed -i "/# Build:.*/c$TXT" projman.tcl cp projman.tcl projman cp changelog-gen.tcl changelog-gen +cp tkregexp.tcl tkregexp ./changelog-gen.tcl --project-name projman --project-version ${VERSION} --project-release ${RELEASE} --out-file debian/changelog --deb --last @@ -25,5 +26,5 @@ dpkg-buildpackage -d #cp ../projman_${VERSION}-${RELEASE}_amd64.deb /files/ -rm -v projman changelog-gen +rm -v projman changelog-gen tkregexp rm -r -v debian/{projman,.debhelper} diff --git a/debian/changelog b/debian/changelog index 52aad62..f7477d7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,26 @@ +projman (2.0.0-beta3) stable; urgency=medium + + * Вынес код связанный с обработкой подсказок при вводе переменных и процедур в отдельный модуль. + * Исправил работу со списком переменных из всплывающего окна. Теперь там можно выбрать из списка стрелками и вставить по Enter. Исправил обработку клавиш Вверх Вниз Ввод Отмена в окне со списком вариантов. + + -- svk Thu, 29 Jan 2026 14:22:55 +0300 + +projman (2.0.0-beta2) stable; urgency=medium + + * Добавлено подключение (bind) сочетания клавиш указанных в настройках инструментов. + * Добавлена динамическая генерация меню 'Инструменты'. Теперь новые внешние инструменты доступны сразу после сохранения файла настроек tools.ini в редакторе. + * Исправлена ошибка с некорректным определением виджета в процедуре получения выделенного текста. + * Добавлено редактирование настроек внешних инструментов. И пункт в меню 'Инструменты'->'Настройки' + * Исправление ошибки с запуском внешних программ. + * Добавлено определение путей до внешних программ при подключении к редактору. + * Сделана обработка шаблонов командной строки и запуск внешних инструментов. + * Добавлен tkregexp для установки в /usr/bin + * Начало работы с внешними инструментами: - Добавлено создание и работа (проверка параметров + * Исправлен скрипт сборки бсд-пакета + * Добавлена сборка пакетов для openbsd + + -- svk Tue, 27 Jan 2026 16:44:48 +0300 + projman (2.0.0-beta1) stable; urgency=medium * Сделан вывод отладочной информации по запросу. @@ -468,3 +491,6 @@ projman (2.0.0-alfa0) stable; urgency=medium + + + diff --git a/debian/install b/debian/install index 954b959..621a7cb 100644 --- a/debian/install +++ b/debian/install @@ -1,5 +1,6 @@ projman /usr/bin/ changelog-gen /usr/bin/ +tkregexp /usr/bin lib/*.tcl /usr/share/projman/lib lib/msgs/* /usr/share/projman/lib/msgs theme /usr/share/projman/ diff --git a/lib/editor.tcl b/lib/editor.tcl index 7e585ee..ff85af6 100644 --- a/lib/editor.tcl +++ b/lib/editor.tcl @@ -16,7 +16,7 @@ namespace eval Editor { $node.frmText.t configure -$optionName $value } } - + # Comment one string or selected string proc Comment {txt fileType} { global lexers cfgVariables @@ -363,15 +363,24 @@ namespace eval Editor { } } - proc SelectionGet {txt} { - variable selectionText + proc SelectionGet {{txt ""}} { + global nbEditor + variable selectionText "" + if {$txt eq ""} { + DebugPuts "Editor::SelectionGet: [focus]" + set txt [focus] + if {![string match -nocase "*text*" $txt]} { + return "" + } + } set selBegin [lindex [$txt tag ranges sel] 0] set selEnd [lindex [$txt tag ranges sel] 1] if {$selBegin ne "" && $selEnd ne ""} { set selectionText [$txt get $selBegin $selEnd] } + return $selectionText } - + proc SelectionHighlight {txt} { variable selectionText $txt tag remove lightSelected 1.0 end @@ -393,236 +402,16 @@ namespace eval Editor { } } - proc VarHelperKey { widget K A } { - set win .varhelper - # if { [winfo exists $win] == 0 } { return } - set ind [$win.lBox curselection] - # puts ">>>>>>>>>>>> VarHelperBind <<<<<<<<<<<<<<<<" - - switch -- $K { - Prior { - set up [expr [$win.lBox index active] - [$win.lBox cget -height]] - if { $up < 0 } { set up 0 } - $win.lBox activate $up - $win.lBox selection clear 0 end - $win.lBox selection set $up $up - } - Next { - set down [expr [$win.lBox index active] + [$win.lBox cget -height]] - if { $down >= [$win.lBox index end] } { set down end } - $win.lBox activate $down - $win.lBox selection clear 0 end - $win.lBox selection set $down $down - } - Up { - set up [expr [$win.lBox index active] - 1] - if { $up < 0 } { set up 0 } - $win.lBox activate $up - $win.lBox selection clear 0 end - $win.lBox selection set $up $up - } - Down { - set down [expr [$win.lBox index active] + 1] - if { $down >= [$win.lBox index end] } { set down end } - $win.lBox activate $down - $win.lBox selection clear 0 end - $win.lBox selection set $down $down - } - Return { - $widget delete "insert - 1 chars wordstart" "insert wordend - 1 chars" - $widget insert "insert" [$win.lBox get [$win.lBox curselection]] - # eval [bind VarHelperBind ] - Editor::VarHelperEscape $widget - } - default { - $widget insert "insert" $A - # eval [bind VarHelperBind ] - Editor::VarHelperEscape $widget - } - } - } ;# proc auto_completition_key - proc VarHelperEscape {w} { - # puts ">>>>>>>>>>>> VarHelperEscape <<<<<<<<<<<<<<<<" - # bindtags $w [list [winfo parent $w] $w Text sysAfter all] - bindtags $w [list [winfo toplevel $w] $w Ctext sysAfter all] - catch { destroy .varhelper } - DebugPuts [bindtags $w] - DebugPuts [bind $w] - } - proc VarHelper {x y w word wordType} { - global editors lexers variables - variable txt - variable win - # set txt $w.frmText.t - # блокировка открытия диалога если запущен другой - set txt $w - # set win .varhelper - # Проверяем если есть выделение то блокировать появление диалога - if {[$txt tag ranges sel] != ""} { - DebugPuts "You have selected text [$txt tag ranges sel]" - return - } - # puts "$x $y $w $word $wordType" - set fileType [dict get $editors $txt fileType] - - if {[dict exists $editors $txt variableList] != 0} { - set varList [dict get $editors $txt variableList] - # puts $varList - } - if {[dict exists $editors $txt procedureList] != 0} { - set procList [dict get $editors $txt procedureList] - } - # puts $procList - # puts ">>>>>>>[dict get $lexers $fileType commands]" - if {[dict exists $lexers $fileType commands] !=0} { - foreach i [dict get $lexers $fileType commands] { - # puts $i - lappend procList $i - } - } - - # if {[dict exists $editors $txt variableList] == 0 && [dict exists $editors $txt procedureList] == 0} { - # return - # } - set findedVars "" - switch -- $wordType { - vars { - foreach i [lsearch -nocase -all $varList $word*] { - # puts [lindex $varList $i] - set item [lindex [lindex $varList $i] 0] - # puts $item - if {[lsearch $findedVars $item] eq "-1"} { - lappend findedVars $item - # puts $item - } - } - } - procedure { - foreach i [lsearch -nocase -all $procList $word*] { - # puts [lindex $varList $i] - set item [lindex [lindex $procList $i] 0] - # puts $item - if {[lsearch $findedVars $item] eq "-1"} { - lappend findedVars $item - # puts $item - } - } - } - default { - foreach i [lsearch -nocase -all $varList $word*] { - # puts [lindex $varList $i] - set item [lindex [lindex $varList $i] 0] - # puts $item - if {[lsearch $findedVars $item] eq "-1"} { - lappend findedVars $item - # puts $item - } - } - foreach i [lsearch -nocase -all $procList $word*] { - # puts [lindex $varList $i] - set item [lindex [lindex $procList $i] 0] - # puts $item - if {[lsearch $findedVars $item] eq "-1"} { - lappend findedVars $item - # puts $item - } - } - } - } - # unset item - # bindtags $txt [list VarHelperBind [winfo toplevel $txt] $txt Ctext sysAfter all] - # bindtags $txt.t [list VarHelperBind [winfo parent $txt.t] $txt.t Text sysAfter all] - # bind VarHelperBind "Editor::VarHelperEscape $txt.t; break" - # # bindtags $txt.t {[list [winfo parent $txt.t] $txt.t Text sysAfter all]}; - # # bindtags $txt {[list [winfo toplevel $txt] $txt Ctext sysAfter all]}; - # # catch { destroy .varhelper }" - # bind VarHelperBind {Editor::VarHelperKey %W %K %A; break} - # - if {$findedVars eq ""} { + proc ReleaseKey {k txt fileType} { + global cfgVariables lexers returnProcessed + # Если Return уже обработан в SelectFromList, пропускаем + # puts "$returnProcessed $k" + if {$k eq "Return" && [info exists returnProcessed]} { + unset returnProcessed return } - # puts $findedVars - VarHelperDialog $x $y $w $word $findedVars - - } - - proc VarHelperDialog {x y w word findedVars} { - global editors lexers variables - variable txt - variable win - # puts ">>>>>>>>>>>>>$x $y $w $word $findedVars" - # set txt $w.frmText.t - # блокировка открытия диалога если запущен другой - # if [winfo exists .findVariables] { - # return - # } - # if { [winfo exists $win] } { destroy $win } - set txt $w - set win .varhelper - # if {$findedVars eq ""} { - # return - # } - toplevel $win - wm transient $win . - wm overrideredirect $win 1 - - listbox $win.lBox -width 30 -border 0 - pack $win.lBox -expand true -fill y -side left - - foreach { item } $findedVars { - $win.lBox insert end $item - } - - catch { $win.lBox activate 0 ; $win.lBox selection set 0 0 } - - if { [set height [llength $findedVars]] > 10 } { set height 10 } - $win.lBox configure -height $height - - # focus $win.lBox - - bind $win { - destroy $Editor::win - focus -force $Editor::txt.t - break - } - bind $win.lBox { - destroy $Editor::win - focus -force $Editor::txt.t - break - } - bind VarHelperBind { - $Editor::txt delete "insert - 1 chars wordstart" "insert wordend - 1 chars" - $Editor::txt insert "insert" [.varhelper.lBox get [.varhelper.lBox curselection]] - # eval [bind VarHelperBind ] - Editor::VarHelperEscape $Editor::txt - break - } - - # Определям расстояние до края экрана (основного окна) и если - # оно меньше размера окна со списком то сдвигаем его вверх - set winGeomY [winfo reqheight $win] - set winGeomX [winfo reqwidth $win] - - set topHeight [winfo height .] - set topWidth [winfo width .] - set topLeftUpperX [winfo x .] - set topLeftUpperY [winfo y .] - set topRightLowerX [expr $topLeftUpperX + $topWidth] - set topRightLowerY [expr $topLeftUpperY + $topHeight] - - if {[expr [expr $x + $winGeomX] > $topRightLowerX]} { - set x [expr $x - $winGeomX] - } - if {[expr [expr $y + $winGeomY] > $topRightLowerY]} { - set y [expr $y - $winGeomY] - } - - wm geom $win +$x+$y - } - - proc ReleaseKey {k txt fileType} { - global cfgVariables lexers + # set pos [$txt index insert] set lineNum [lindex [split $pos "."] 0] set posNum [lindex [split $pos "."] 1] @@ -636,8 +425,53 @@ namespace eval Editor { unset lpos $txt tag remove lightSelected 1.0 end - if { [winfo exists .varhelper] } { destroy .varhelper } - # puts $k + # Обработка ввода для показа окна с подсказками. + # if { [winfo exists .varhelper] } { destroy .varhelper } + # Флаг, нужно ли показывать новый список + set showNewList 1 + + # Проверяем окно списка + if {[winfo exists .varhelper]} { + # Определяем, какая клавиша отпущена + switch -- $k { + Up - Down { + # Стрелки - управление списком, окно остается + # НЕ показываем новый список + set showNewList 0 + return + } + Return { + # Enter - выберет элемент, окно закроется в SelectFromList + # НЕ показываем новый список + set showNewList 0 + return + } + Escape { + # Escape - закрыть окно + destroy .varhelper + set ::Helper::listActive 0 + Helper::VarHelperBindingsRestore $txt + # НЕ показываем новый список + set showNewList 0 + return + } + Control_L - Control_R - Alt_L - Alt_R - Shift_L - Shift_R { + # Модификаторы - окно остается + # НЕ показываем новый список + set showNewList 0 + return + } + default { + # Любая другая клавиша (буквы, цифры, пробел, Tab и т.д.) + # закрывает окно списка, но ПОКАЗЫВАЕМ новый список + destroy .varhelper + set ::Helper::listActive 0 + Helper::VarHelperBindingsRestore $txt + # showNewList остается = 1 (показываем новый список) + } + } + } + switch $k { Return { regexp {^(\s*)} [$txt get [expr $lineNum - 1].0 [expr $lineNum - 1].end] -> spaceStart @@ -681,10 +515,68 @@ namespace eval Editor { if {$cfgVariables(variableHelper) eq "true"} { if {[dict exists $lexers $fileType variableSymbol] != 0} { set varSymbol [dict get $lexers $fileType variableSymbol] - set lastSymbol [string last $varSymbol [$txt get $lineNum.0 $pos]] + set lineText [$txt get $lineNum.0 $pos] + # # Ищем переменную с помощью регулярного выражения + # # Паттерн ищет $ за которым идет имя переменной И курсор сразу после имени + # if {[regexp "(\\$)(\[a-zA-Z_:\]\[a-zA-Z0-9_:\]*)\$" $lineText -> symbol varName]} { + # # Проверяем, что найденный $ - это действительно начало переменной + # # (а не часть строки или другого символа) + # DebugPuts "Found variable: $symbol$varName" + # + # # Дополнительная проверка: перед $ не должно быть обратного слэша (экранирование) + # set posOfVarSymbol [string last $varSymbol $lineText] + # if {$posOfVarSymbol > 0} { + # set charBefore [string index $lineText [expr {$posOfVarSymbol - 1}]] + # if {$charBefore eq "\\"} { + # DebugPuts "Dollar sign is escaped, skipping" + # return + # } + # } + # Helper::VarHelper $box_x $box_y $txt $varName vars + # } + DebugPuts "Line text: '$lineText'" + + # Проверяем, есть ли $ в строке + set lastSymbol [string last $varSymbol $lineText] if {$lastSymbol ne "-1"} { - set word [string trim [$txt get $lineNum.[expr $lastSymbol + 1] $pos]] - Editor::VarHelper $box_x $box_y $txt $word vars + # Проверяем экранирование + if {$lastSymbol > 0} { + set charBefore [string index $lineText [expr {$lastSymbol - 1}]] + if {$charBefore eq "\\"} { + DebugPuts "Dollar sign is escaped, skipping" + return + } + } + # Берем текст после $ + set afterDollar [string range $lineText [expr {$lastSymbol + 1}] end] + DebugPuts "Text after $varSymbol: '$afterDollar'" + # Если после $ ничего нет (только что ввели $) - показываем все переменные + if {$afterDollar eq ""} { + DebugPuts "Just typed $varSymbol, showing all variables" + Helper::VarHelper $box_x $box_y $txt "" vars + return + } + # Проверяем, что введено после $ + if {[regexp {^([a-zA-Z_:][a-zA-Z0-9_:]*)?$} $afterDollar -> varName]} { + # Вариант 1: regexp с концом строки - курсор сразу после (возможного) имени + DebugPuts "Cursor after variable name (or $varSymbol only): '$varName'" + Helper::VarHelper $box_x $box_y $txt $varName vars + } elseif {[regexp {^([a-zA-Z_:][a-zA-Z0-9_:]*)} $afterDollar -> varName]} { + # Вариант 2: есть имя переменной, но курсор не обязательно сразу после + DebugPuts "Found variable name: '$varName'" + # Проверяем позицию курсора + set varEndPos [expr {[string length $varName] + 1}] ; # +1 для $ + + if {$varEndPos == [string length $lineText]} { + # Курсор сразу после имени + Helper::VarHelper $box_x $box_y $txt $varName vars + } else { + DebugPuts "Cursor not immediately after variable name, skipping" + } + } else { + # После $ что-то недопустимое (например, цифра, скобка и т.д.) + DebugPuts "Invalid characters after $varSymbol" + } } } else { set ind [$txt search -backwards -regexp {\W} $pos {insert linestart}] @@ -696,8 +588,9 @@ namespace eval Editor { # set ind [$txt search -backwards -regexp {^} $pos {insert linestart}] set word [$txt get {insert linestart} $pos] } + DebugPuts "> Extracted word: '$word'" if {$word ne ""} { - Editor::VarHelper $box_x $box_y $txt $word {} + Helper::VarHelper $box_x $box_y $txt $word {} } } } @@ -713,7 +606,7 @@ namespace eval Editor { set word [$txt get {insert linestart} $pos] } if {$word ne ""} { - Editor::VarHelper $box_x $box_y $txt $word procedure + Helper::VarHelper $box_x $box_y $txt $word procedure } } } diff --git a/lib/files.tcl b/lib/files.tcl index fe4383b..df4cdb3 100644 --- a/lib/files.tcl +++ b/lib/files.tcl @@ -391,6 +391,12 @@ namespace eval FileOper { if {[file tail $filePath] eq "projman.ini"} { Config::read $dir(cfg) } + if {[file tail $filePath] eq "tools.ini"} { + Tools::Read $dir(cfg) + Tools::CheckVariables + Tools::GetMenu .popup.tools + Tools::GetMenu .frmMenu.mnuTools.m + } if [string match "*untitled*" $nbEditorItem] { FileOper::Close if {$type ne "close"} { diff --git a/lib/gui.tcl b/lib/gui.tcl index 3f26659..d5f0a8f 100644 --- a/lib/gui.tcl +++ b/lib/gui.tcl @@ -135,7 +135,11 @@ GetEditMenu [menu .frmMenu.mnuEdit.m] ttk::menubutton .frmMenu.mnuView -text [::msgcat::mc "View"] -menu .frmMenu.mnuView.m GetViewMenu [menu .frmMenu.mnuView.m] -pack .frmMenu.mnuFile .frmMenu.mnuEdit .frmMenu.mnuView -side left +ttk::menubutton .frmMenu.mnuTools -text [::msgcat::mc "Tools"] -menu .frmMenu.mnuTools.m +Tools::GetMenu [menu .frmMenu.mnuTools.m] + + +pack .frmMenu.mnuFile .frmMenu.mnuEdit .frmMenu.mnuView .frmMenu.mnuTools -side left ttk::menubutton .frmMenu.mnuHelp -text [::msgcat::mc "Help"] -menu .frmMenu.mnuHelp.m GetHelpMenu [menu .frmMenu.mnuHelp.m] @@ -151,6 +155,9 @@ GetFileMenu .popup.file menu .popup.view .popup add cascade -label [::msgcat::mc "View"] -menu .popup.view GetViewMenu .popup.view +menu .popup.tools +.popup add cascade -label [::msgcat::mc "Tools"] -menu .popup.tools +Tools::GetMenu .popup.tools set frmTool [ttk::frame .frmBody.frmTool] ttk::panedwindow .frmBody.panel -orient horizontal -style TPanedwindow diff --git a/lib/helper.tcl b/lib/helper.tcl new file mode 100644 index 0000000..dadd7c1 --- /dev/null +++ b/lib/helper.tcl @@ -0,0 +1,340 @@ +namespace eval Helper { + variable ::originalBindings {} + # Флаг, указывающий, что окно со списком активно + variable ::listActive 0 + # Переменная для отслеживания предыдущего ввода (чтобы не обновлять список без необходимости) + variable ::previousInput "" + + proc VarHelperKey { widget K A } { + set win .varhelper + DebugPuts "Helper::VarHelperKey: K=$K, A='$A'" + + # Проверяем, существует ли окно списка + if {![winfo exists $win]} { + DebugPuts "Window doesn't exist, restoring bindings" + Helper::VarHelperBindingsRestore $widget + set ::listActive 0 + return + } + + switch -- $K { + { + DebugPuts "Processing Up arrow" + # Перемещаем выбор вверх + set current [$win.lBox curselection] + DebugPuts "Current selection: $current" + + if {$current ne "" && $current > 0} { + $win.lBox selection clear 0 end + $win.lBox selection set [expr {$current - 1}] + $win.lBox activate [expr {$current - 1}] + $win.lBox see [expr {$current - 1}] + } elseif {[$win.lBox size] > 0} { + # Если ничего не выбрано, выбираем последний элемент + set last [expr {[$win.lBox size] - 1}] + $win.lBox selection clear 0 end + $win.lBox selection set $last + $win.lBox activate $last + $win.lBox see $last + } + return -code break + } + { + DebugPuts "Processing Down arrow" + # Перемещаем выбор вниз + set current [$win.lBox curselection] + set size [$win.lBox size] + DebugPuts "Current selection: $current, size: $size" + + if {$current ne "" && $current < $size - 1} { + $win.lBox selection clear 0 end + $win.lBox selection set [expr {$current + 1}] + $win.lBox activate [expr {$current + 1}] + $win.lBox see [expr {$current + 1}] + } elseif {$size > 0} { + # Если ничего не выбрано, выбираем первый элемент + $win.lBox selection clear 0 end + $win.lBox selection set 0 + $win.lBox activate 0 + $win.lBox see 0 + } + return -code break + } + { + DebugPuts "Processing Return" + Helper::SelectFromList $widget + return -code break + } + { + DebugPuts "Processing Escape" + # Закрываем окно списка + wm withdraw $win + set ::listActive 0 + Helper::VarHelperBindingsRestore $widget + set ::previousInput "" + focus $widget + return -code break + } + default { + DebugPuts "Default case for K=$K, A='$A'" + # Для печатных символов + if {$A ne ""} { + DebugPuts "Inserting character '$A' and restoring bindings" + # Восстанавливаем привязки перед вставкой + Helper::VarHelperBindingsRestore $widget + # Вставляем символ + $widget insert "insert" $A + } + return -code break + } + } + } + + proc VarHelper {x y w word wordType} { + global editors lexers variables + variable txt + variable win + + DebugPuts "=== VarHelper called: word='$word', wordType='$wordType' ===" + + set txt $w + # Проверяем если есть выделение то блокировать появление диалога + if {[$txt tag ranges sel] != ""} { + DebugPuts "You have selected text [$txt tag ranges sel]" + return + } + + set fileType [dict get $editors $txt fileType] + + if {[dict exists $editors $txt variableList] != 0} { + set varList [dict get $editors $txt variableList] + } else { + set varList {} + } + if {[dict exists $editors $txt procedureList] != 0} { + set procList [dict get $editors $txt procedureList] + } else { + set procList {} + } + + if {[dict exists $lexers $fileType commands] != 0} { + foreach i [dict get $lexers $fileType commands] { + lappend procList $i + } + } + + set findedVars "" + switch -- $wordType { + vars { + foreach i [lsearch -nocase -all $varList $word*] { + set item [lindex [lindex $varList $i] 0] + if {[lsearch $findedVars $item] eq "-1"} { + lappend findedVars $item + } + } + } + procedure { + foreach i [lsearch -nocase -all $procList $word*] { + set item [lindex [lindex $procList $i] 0] + if {[lsearch $findedVars $item] eq "-1"} { + lappend findedVars $item + } + } + } + default { + foreach i [lsearch -nocase -all $varList $word*] { + set item [lindex [lindex $varList $i] 0] + if {[lsearch $findedVars $item] eq "-1"} { + lappend findedVars $item + } + } + foreach i [lsearch -nocase -all $procList $word*] { + set item [lindex [lindex $procList $i] 0] + if {[lsearch $findedVars $item] eq "-1"} { + lappend findedVars $item + } + } + } + } + + DebugPuts "Found [llength $findedVars] items: $findedVars" + + if {$findedVars eq ""} { + DebugPuts "No items found, returning" + return + } + + VarHelperDialog $x $y $w $word $findedVars + } + + proc VarHelperDialog {x y w word findedVars} { + variable txt + variable win + + set txt $w + set win .varhelper + + DebugPuts "VarHelperDialog called with [llength $findedVars] items" + + # Если окно уже существует, уничтожаем его + if {[winfo exists $win]} { + DebugPuts "Window already exists, destroying it" + destroy $win + Helper::VarHelperBindingsRestore $txt + } + + toplevel $win + wm transient $win . + wm overrideredirect $win 1 + + listbox $win.lBox -width 30 -border 0 + pack $win.lBox -expand true -fill y -side left + + foreach item $findedVars { + $win.lBox insert end $item + } + + DebugPuts "Listbox created with [llength $findedVars] items" + + # Выбираем первый элемент + if {[llength $findedVars] > 0} { + $win.lBox selection set 0 + $win.lBox activate 0 + } + + if {[set height [llength $findedVars]] > 10} { + set height 10 + } + $win.lBox configure -height $height + + Helper::VarHelperBindingsSetup $w + + # Привязка для закрытия окна списка + bind $win [list apply {{win txt} { + set ::listActive 0 + Helper::VarHelperBindingsRestore $txt + set ::previousInput "" + }} $win $txt.t] + + # Определяем расстояние до края экрана (основного окна) и если + # оно меньше размера окна со списком то сдвигаем его вверх + set winGeomY [winfo reqheight $win] + set winGeomX [winfo reqwidth $win] + + set topHeight [winfo height .] + set topWidth [winfo width .] + set topLeftUpperX [winfo x .] + set topLeftUpperY [winfo y .] + set topRightLowerX [expr $topLeftUpperX + $topWidth] + set topRightLowerY [expr $topLeftUpperY + $topHeight] + + if {[expr $x + $winGeomX] > $topRightLowerX} { + set x [expr $x - $winGeomX] + } + if {[expr $y + $winGeomY] > $topRightLowerY} { + set y [expr $y - $winGeomY] + } + set ::listActive 1 + DebugPuts "Showing window at +$x+$y" + wm geom $win +$x+$y + } + + proc VarHelperBindingsSetup {txt} { + DebugPuts "Setting up bindings for $txt" + + # Сбрасываем сохраненные привязки + set ::originalBindings {} + + # Список событий для перехвата + set events { } + + # Сохраняем и заменяем привязки + foreach event $events { + # Получаем текущую привязку + set original [bind $txt $event] + DebugPuts " Saving binding for $event: '$original'" + + # Сохраняем оригинал + lappend ::originalBindings [list $event $original] + + # Устанавливаем новую привязку + bind $txt $event [list Helper::VarHelperKey $txt $event %A] + } + } + + # Восстановление оригинальных привязок + proc VarHelperBindingsRestore {txt} { + DebugPuts "Restoring bindings for $txt" + DebugPuts "Have [llength $::originalBindings] bindings to restore" + + if {![info exists ::originalBindings]} { + DebugPuts " No original bindings stored" + # Очищаем наши привязки + foreach event { } { + bind $txt $event {} + } + return + } + + # Восстанавливаем оригинальные привязки + foreach binding $::originalBindings { + set event [lindex $binding 0] + set command [lindex $binding 1] + DebugPuts " Restoring $event: '$command'" + + if {$command eq ""} { + bind $txt $event {} + } else { + bind $txt $event $command + } + } + + # Очищаем сохраненные привязки + set ::originalBindings {} + } + + proc SelectFromList {txt} { + global returnProcessed editors lexers + set win .varhelper + # puts "[dict get $editors $txt fileType]" + # puts "[dict get $lexers [dict get $editors $txt fileType] variableSymbol]" + + if {![winfo exists $win]} { + return + } + set selected [$win.lBox curselection] + if {$selected ne ""} { + set text [string trim [$win.lBox get $selected]] + # DebugPuts "[dict exists $lexers [dict get $editors $txt fileType] variableSymbol]" + if [dict exists $lexers [dict get $editors $txt fileType] variableSymbol] { + set varSymbol [dict get $lexers [dict get $editors $txt fileType] variableSymbol] + } else { + set varSymbol "" + } + # Опеределяем что символ перед позицией вставки равен символу переменной из настроек lexers + # если равен то вставляем выбранное из списка сразу за ним + # если нет то удаляем введенный текст до этого символа и вставляем выбранное из списка + if {[$txt get "insert - 1 char" "insert"] eq $varSymbol} { + $txt insert "insert" $text + } else { + $txt delete "insert - 1 chars wordstart" "insert wordend - 1 chars" + $txt insert "insert" $text + } + # Закрываем окно + destroy $win + set ::listActive 0 + Helper::VarHelperBindingsRestore $txt + set ::previousInput "" + + # Устанавливаем флаг, что Return уже обработан + set returnProcessed 1 + # after 10 {catch {unset returnProcessed}} + + focus $txt.t + } + } + proc DebugPuts {msg} { + puts "DEBUG: $msg" + } +} + diff --git a/lib/imgviewer.tcl b/lib/imgviewer.tcl index 436cf66..6c143c1 100644 --- a/lib/imgviewer.tcl +++ b/lib/imgviewer.tcl @@ -19,7 +19,7 @@ proc ImageViewer {f w node} { #$w.scrwin setwidget $w.scrwin.f openImg $f $w.f.c $node } - + proc openImg {fn w node} { global im1 factor set im1 [image create photo -file $fn] diff --git a/lib/msgs/en.msg b/lib/msgs/en.msg index 1840432..72cadfe 100644 --- a/lib/msgs/en.msg +++ b/lib/msgs/en.msg @@ -174,6 +174,7 @@ ::msgcat::mcset en "Title normal" ::msgcat::mcset en "Title modify" ::msgcat::mcset en "Toolbar" +::msgcat::mcset en "Tools" ::msgcat::mcset en "Undo" ::msgcat::mcset en "Update" ::msgcat::mcset en "Variables" @@ -185,4 +186,3 @@ ::msgcat::mcset en "Work dir" - diff --git a/lib/msgs/ru.msg b/lib/msgs/ru.msg index bff7471..0991f34 100644 --- a/lib/msgs/ru.msg +++ b/lib/msgs/ru.msg @@ -214,6 +214,7 @@ ::msgcat::mcset ru "Title normal" "Файл нормальный" ::msgcat::mcset ru "Title modify" "Файл изменен" ::msgcat::mcset ru "Toolbar" "Панель инструментов" +::msgcat::mcset ru "Tools" "Инструменты" ::msgcat::mcset ru "User name" "Имя пользователя" ::msgcat::mcset ru "Undo" "Отменить" ::msgcat::mcset ru "Update" "Обновить" diff --git a/lib/tools.tcl b/lib/tools.tcl new file mode 100644 index 0000000..0051bff --- /dev/null +++ b/lib/tools.tcl @@ -0,0 +1,220 @@ +################################################################## +# tools.tcl - this file implements the logic of working +# with external tools. +################################################################## +# svk, 01/2026 +################################################################## + +namespace eval Tools {} { + variable toolsINISections + variable toolsVariables +} + +set ::toolsDefault "\[VisualRegexp\] +commandString=tkregexp \"%s\" +description=A graphical front-end to write/debug regular expression +icon= +shortCut= +\[TkDIFF\] +commandString=tkdiff %f %f +description=TkDiff is a Tcl/Tk front-end to diff +icon= +shortCut= +" +# Создание файла настроек внешних инструментов +proc Tools::Create {dir} { + set toolsFile [open [file join $dir tools.ini] "w+"] + puts $toolsFile $::toolsDefault + close $toolsFile +} + +proc Tools::Read {dir} { + set ::toolsVariables "" + set toolsFile [ini::open [file join $dir tools.ini] "r"] + foreach section [ini::sections $toolsFile] { + foreach key [ini::keys $toolsFile $section] { + lappend ::toolsINIsections($section) $key + dict set ::toolsVariables $section $key [ini::value $toolsFile $section $key] + DebugPuts "Tools::Read: $toolsFile $section $key = [ini::value $toolsFile $section $key]" + } + } + ini::close $toolsFile +} + +proc Tools::Write {dir} { + set toolsFile [ini::open [file join $dir tools.ini] "w"] + foreach section [array names ::toolsINIsections] { + dict for {key value} [dict get $::toolsVariables $section] { + DebugPuts "Tools::write: $section $key = $value" + # ini::set $toolsFile $section $key [dict get $::toolsVariables $section $key] + ini::set $toolsFile $section $key $value + } + } + ini::commit $toolsFile + ini::close $toolsFile +} + +# Добавление перменной в список +# если отсутствует нужная секция то она будет добавлена. +proc Tools::AddVariable {key value section} { + # Проверяем, существует ли уже такая переменная + if {[info exists ::toolsVariables($key)]} { + DebugPuts "The variable '$key' already exists: " + return 0 + } + # Добавляем в массив переменных + # set ::toolsVariables($key) $value + dict set ::toolsVariables $section $key $value + # Добавляем в список ключей секции + if {[dict exists $::toolsVariables $key]} { + # Проверяем, нет ли уже такого ключа в секции + if {[lsearch -exact $::toolsINIsections($section) $key] == -1} { + lappend ::toolsINIsections($section) $key + } + } else { + set ::toolsINIsections($section) [list $key] + } + DebugPuts "Tools::AddVariable: The variable '$key' has been added to the '$section' array 'toolsVariables' with value '$value'" + + return 1 +} + +# Проверяем наличие переменных в tools.ini на основе "эталонного" списка +# и выставляем значение по умолчанию если в конфиге переменной нет +proc Tools::CheckVariables {} { + set valList [split $::toolsDefault "\n"] + foreach item $valList { + if {[regexp -nocase -all -- {\[(\w+)\]} $item -> v1]} { + set section $v1 + } + if {[regexp {^([^=]+)=(.*)$} $item -> key value]} { + # puts "$section $key $value >> [dict get $::toolsVariables $section $key]" + # if {[dict get $::toolsVariables $section $key] eq ""} + if ![dict exists $::toolsVariables $section $key] { + DebugPuts "Error in Tools::CheckVariables: variable $section $key not found" + Tools::AddVariable "$key" "$value" "$section" + # DebugPuts "Tools::CheckVariables: The variable toolsVariables $key setting to default value \"$value\"" + } + } + } + foreach id [dict keys $::toolsVariables] { + DebugPuts "Tools::CheckVariables: config parameters for $id [dict get $::toolsVariables $id]!" + } + # DebugPuts "toolsVariables dict keys: [dict keys $::toolsVariables]" +} + +proc Tools::GetMenu {m} { + global cfgVariables toolsVariables + set count [$m index end] + if {$count != "none"} { + for {set i $count} {$i >= 0} {incr i -1} { + $m delete $i + } + } + foreach toolName [dict keys $toolsVariables] { + dict for {key value} [dict get $toolsVariables $toolName] { + DebugPuts "GetToolsMenu $key $value" + if {$key eq "commandString"} { + set cmd "$value" + } + if {$key eq "shortCut"} { + set shortCut "$value" + } + } + if {[info exists cmd] == 1 && $cmd ne ""} { + if {[info exists shortCut] == 1 && $shortCut ne ""} { + $m add command -label $toolName -accelerator $shortCut -command [list Tools::Execute "$toolName"] + bind . <$shortCut> "[list Tools::Execute "$toolName"]; break" + } else { + $m add command -label $toolName -command [list Tools::Execute "$toolName"] + } + } + } + $m add separator + $m add command -label "[::msgcat::mc "Settings"]" -command Tools::Settings +} + +proc Tools::CommandPathSettings {command} { + global tcl_platform + if [file exists $command] {return $command} + + if {$tcl_platform(platform) eq "windows"} { + set cmd "where $command)" + } else { + set cmd "which $command" + } + DebugPuts [catch {exec {*}$cmd} toolsPath] + DebugPuts "executor_path $toolsPath" + if {[catch {exec {*}$cmd} toolsPath]} { + DebugPuts "Tools::CommandPathSettings: Программа $command не найдена в системе" + return "" + } + set fullPath [string trim $toolsPath] + set firstPath [lindex [split $toolsPath "\n"] 0] + + DebugPuts "Tools::CommandPathSettings: executable path $fullPath" + return $fullPath +} + +proc Tools::Execute {toolName} { + global cfgVariables toolsVariables tree + if ![dict exists $::toolsVariables $toolName commandString] { + DebugPuts "Tools::Execute: command for $toolName not found" + return + } else { + set command [dict get $::toolsVariables $toolName commandString] + DebugPuts "Tools::Execute: command for $toolName as $command" + } + # 7. Проверять команды на доступность в системе и подставлять полный путь к команде + # если в конфиге не указан полный путь. + # Проверем наличие внешгних программ в системе + set cmd [lindex [split $command " "] 0] + if [file exists $cmd] { + set fullCommand $command + } else { + set fullPathToExec [Tools::CommandPathSettings "$cmd"] + set fullCommand [lreplace [split $command " "] 0 0 $fullPathToExec] + } + if {$fullPathToExec eq ""} { + DebugPuts "Tools::Execute: $command not found" + return + } else { + DebugPuts "Tools::Execute: $fullPathToExec, $fullCommand" + } + # 2. Определять выделен ли текст в открытом редакторе + # 5. Заменяем %s на выделенный в редакторе текст + set selectedText [Editor::SelectionGet] + if {$selectedText ne ""} { + regsub -all "%s" $fullCommand "$selectedText" fullCommand + DebugPuts "Tools::Execute: selected text \"$selectedText\", command \"$fullCommand\"" + } + + # 1. Определять текущий файл + # 3. Опеределять сколько файлов выделено в дереве + # 4. Заменяем знак %f на имя текущего файла (файлов) + # regsub -all "%f" $command "$filePath" fullCommand + set filesList [Tree::GetSelectedItemValues $tree] + if {$filesList ne ""} { + foreach file $filesList { + # Если больше нет %f для замены, выходим из цикла + if {![string match "*%f*" $fullCommand]} break + set fullCommand [regsub {%f} $fullCommand $file] + } + } + + # 6. Заменяем %d на текущий каталог(и), если он выделен в дереве, + # и если не выделено то корневой открытый в дереве + DebugPuts "Tools::Execute: $fullCommand" + + set pipe [open "|$fullCommand" "r"] + fileevent $pipe readable + fconfigure $pipe -buffering none -blocking no +} + +# Правка файла настроек +proc Tools::Settings {} { + global dir + + FileOper::Edit [file join $dir(cfg) tools.ini] + # Config::read $dir(cfg) +} diff --git a/lib/tree.tcl b/lib/tree.tcl index 920c63a..2fc99ab 100644 --- a/lib/tree.tcl +++ b/lib/tree.tcl @@ -99,6 +99,7 @@ namespace eval Tree { proc PressItem {tree} { global nbEditor lexers editors activeProject set id [$tree selection] + if {[llength $id] > 1} {return} $tree tag remove selected $tree item $id -tags selected SetActiveProject [GetItemID $tree [GetUpperItem $tree $id]] @@ -160,5 +161,12 @@ namespace eval Tree { GetUpperItem $tree $parent } } - + + proc GetSelectedItemValues {tree} { + set valuesList "" + foreach itemID [$tree selection] { + lappend valuesList [GetItemID $tree $itemID] + } + return $valuesList + } } diff --git a/openbsd/build-package-bsd.sh b/openbsd/build-package-bsd.sh index 7f32f8d..78c1f95 100755 --- a/openbsd/build-package-bsd.sh +++ b/openbsd/build-package-bsd.sh @@ -49,7 +49,6 @@ cat > ${WORK_DIR}/${PKG_FULLNAME}/+CONTENTS << EOF @depend devel/tklib:tklib-*:tcl-* @comment Editor for Tcl/Tk and other languages. @arch amd64 -@wantlib pthread @ignore @cwd /usr/local EOF @@ -78,7 +77,7 @@ Supported languages for highlighting and navigation: Tcl/Tk, GO, Perl, Python, Ruby, Shell (BASH), Markdown, YAML (Ansible), Lua. EOF -(cd ${WORK_DIR} && tar -czf ../../../${PKG_FULLNAME}.tgz ${PKG_FULLNAME}/) +(cd ${WORK_DIR}/${PKG_FULLNAME}/ && pwd && ls -1 && tar -czf ../../../../${PKG_FULLNAME}.tgz .) echo "Package created: ${PKG_FULLNAME}.tgz" diff --git a/projman.tcl b/projman.tcl index 18ad1ef..519f949 100755 --- a/projman.tcl +++ b/projman.tcl @@ -9,8 +9,8 @@ exec wish8.6 "$0" -- "$@" # Home page: https://nuk-svk.ru ###################################################### # Version: 2.0.0 -# Release: beta1 -# Build: 22012026174911 +# Release: beta3 +# Build: 29012026155729 ###################################################### # определим текущую версию, релиз и т.д. @@ -114,13 +114,22 @@ foreach modFile [lsort [glob -nocomplain [file join $dir(theme) *]]] { } -# загружаем пользовательский конфиг, если он отсутствует, то копируем дефолтный -if {[file exists [file join $dir(cfg) projman.ini]] ==0} { +# загружаем пользовательский конфиг, если он отсутствует или пустой, то копируем дефолтный +if {[file exists [file join $dir(cfg) projman.ini]] == 0 || [file size [file join $dir(cfg) projman.ini]] == 0} { Config::create $dir(cfg) } Config::read $dir(cfg) Config::CheckVariables +# загружаем пользовательский конфиг для инстурментов, если он отсутствует или пустой, то копируем дефолтный +if {[file exists [file join $dir(cfg) tools.ini]] == 0 || [file size [file join $dir(cfg) tools.ini]] == 0} { + Tools::Create $dir(cfg) +} +# Читаем настройки для внешних инструментов +Tools::Read $dir(cfg) +Tools::CheckVariables +Tools::Write $dir(cfg) + ::msgcat::mclocale $cfgVariables(locale) if [::msgcat::mcload [file join $dir(lib) msgs]] {