5 Commits

Author SHA1 Message Date
cee8f57ab7 - Bug in Funktionanzeige behoben
- Auszuschliessende Verzeichnisse in Projektfunktionen hinzugefügt
- Dateien können als Paramter übergeben werden
- Englisch hinzugefügt
2026-08-25 21:57:23 +02:00
04c5212d54 Dateien, die als Parameter übergeben werden, werden nach dem Start direkt geladen 2026-08-25 14:56:27 +02:00
7264b68f11 BareCode-v1.2.tar.gz entfernt 2026-08-25 14:46:39 +02:00
a1b59ff051 Bug in Anzeige der definierten Variablenanzeige behoben 2026-08-25 14:45:51 +02:00
bd40a583cb Englisch hinzugefügt 2026-08-25 14:28:39 +02:00
139 changed files with 4217 additions and 4477 deletions

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16)
project(BareCode
VERSION 1.2.0
VERSION 1.3.0
DESCRIPTION "A modular code editor built with Qt6"
LANGUAGES CXX
)
@@ -16,8 +16,6 @@ set(CMAKE_AUTOUIC ON)
# ---------------------------------------------------------------------------
# Plattform-Erkennung
# Haiku definiert kein eigenes CMake-Flag — wir erkennen es über den
# Systemnamen. CMAKE_SYSTEM_NAME ist "Haiku" auf Haiku OS.
# ---------------------------------------------------------------------------
if(WIN32)
set(PLATFORM_WINDOWS TRUE)
@@ -37,41 +35,60 @@ endif()
# ---------------------------------------------------------------------------
# Qt6
# ---------------------------------------------------------------------------
find_package(Qt6 REQUIRED COMPONENTS
Core
Gui
Widgets
Concurrent
)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Concurrent)
qt_standard_project_setup()
# Include cmake modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# Collect sources
add_subdirectory(src)
# ---------------------------------------------------------------------------
# Übersetzungen — lrelease → .qm → ins Build-Verzeichnis
# ---------------------------------------------------------------------------
find_program(LRELEASE_EXECUTABLE
NAMES lrelease lrelease-qt6
HINTS "${Qt6_DIR}/../../../bin"
REQUIRED
)
set(TS_FILES
${CMAKE_CURRENT_SOURCE_DIR}/translations/barecode_de.ts
${CMAKE_CURRENT_SOURCE_DIR}/translations/barecode_en.ts
)
set(QM_FILES)
foreach(TS_FILE ${TS_FILES})
get_filename_component(TS_NAME ${TS_FILE} NAME_WE)
set(QM_FILE "${CMAKE_CURRENT_BINARY_DIR}/translations/${TS_NAME}.qm")
add_custom_command(
OUTPUT "${QM_FILE}"
COMMAND ${CMAKE_COMMAND} -E make_directory
"${CMAKE_CURRENT_BINARY_DIR}/translations"
COMMAND ${LRELEASE_EXECUTABLE} "${TS_FILE}" -qm "${QM_FILE}"
DEPENDS "${TS_FILE}"
COMMENT "lrelease: ${TS_NAME}.qm"
VERBATIM
)
list(APPEND QM_FILES "${QM_FILE}")
endforeach()
add_custom_target(BareCode_translations ALL DEPENDS ${QM_FILES})
# ---------------------------------------------------------------------------
# Resources
# ---------------------------------------------------------------------------
qt_add_resources(BARECODE_RESOURCES resources/resources.qrc)
# ---------------------------------------------------------------------------
# Executable
# Unter Windows: .rc-Datei für EXE-Icon einbinden
# ---------------------------------------------------------------------------
if(PLATFORM_WINDOWS)
qt_add_executable(BareCode
main.cpp
BareCode.rc
${BARECODE_RESOURCES}
)
qt_add_executable(BareCode main.cpp BareCode.rc ${BARECODE_RESOURCES})
else()
qt_add_executable(BareCode
main.cpp
${BARECODE_RESOURCES}
)
qt_add_executable(BareCode main.cpp ${BARECODE_RESOURCES})
endif()
add_dependencies(BareCode BareCode_translations)
target_link_libraries(BareCode PRIVATE
BareCode_Core
BareCode_Editor
@@ -99,7 +116,11 @@ install(FILES LICENSE
DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/barecode
)
# ---- Linux (FreeDesktop) --------------------------------------------------
# .qm-Dateien installieren
install(FILES ${QM_FILES}
DESTINATION ${CMAKE_INSTALL_DATADIR}/BareCode/translations
)
if(PLATFORM_LINUX)
foreach(SIZE 16 32 48 64 128 256 512)
install(FILES resources/icon_${SIZE}.png
@@ -107,35 +128,27 @@ if(PLATFORM_LINUX)
RENAME barecode.png
)
endforeach()
install(FILES BareCode.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
)
endif()
# ---- Haiku ----------------------------------------------------------------
# Haiku verwendet kein FreeDesktop-System. Icons und MIME-Typen werden
# nativ über 'mimeset' gesetzt. Die PNG-Icons legen wir in den
# Haiku-typischen Pfad, ein post-install Skript ruft mimeset auf.
if(PLATFORM_HAIKU)
install(FILES resources/icon_256.png
DESTINATION ${CMAKE_INSTALL_DATADIR}/BareCode
RENAME BareCode.png
)
# mimeset nach der Installation ausführen um MIME-Typ zu registrieren
install(CODE "
execute_process(
COMMAND mimeset -f \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/BareCode\"
RESULT_VARIABLE _mimeset_result
)
if(NOT _mimeset_result EQUAL 0)
message(WARNING \"mimeset konnte nicht ausgeführt werden MIME-Typ muss manuell gesetzt werden.\")
message(WARNING \"mimeset konnte nicht ausgeführt werden.\")
endif()
")
endif()
# ---- macOS ----------------------------------------------------------------
if(PLATFORM_MACOS)
set_target_properties(BareCode PROPERTIES
MACOSX_BUNDLE TRUE
@@ -144,4 +157,3 @@ if(PLATFORM_MACOS)
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
)
endif()

View File

@@ -1,7 +1,7 @@
# BareCode
**Schlanker, modularer Code-Editor für Web-Entwickler**
Version 1.2.0 — von **Dany Thinnes** | [Projekt Hirnfrei](https://www.projekt-hirnfrei.de) | [Discord](https://discord.projekt-hirnfrei.de)
Version 1.3.0 — von **Dany Thinnes** | [Projekt Hirnfrei](https://www.projekt-hirnfrei.de) | [Discord](https://discord.projekt-hirnfrei.de)
Entwickelt mit **C++17**, **Qt6** und **CMake**.
Kompiliert auf **Linux**, **Windows** und **Haiku**.

View File

@@ -1,2 +1,3 @@
{src/
build/
BareCodeAUR

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16)
project(BareCode
VERSION 1.1.0
VERSION 1.2.0
DESCRIPTION "A modular code editor built with Qt6"
LANGUAGES CXX
)
@@ -16,8 +16,6 @@ set(CMAKE_AUTOUIC ON)
# ---------------------------------------------------------------------------
# Plattform-Erkennung
# Haiku definiert kein eigenes CMake-Flag wir erkennen es über den
# Systemnamen. CMAKE_SYSTEM_NAME ist "Haiku" auf Haiku OS.
# ---------------------------------------------------------------------------
if(WIN32)
set(PLATFORM_WINDOWS TRUE)
@@ -37,41 +35,60 @@ endif()
# ---------------------------------------------------------------------------
# Qt6
# ---------------------------------------------------------------------------
find_package(Qt6 REQUIRED COMPONENTS
Core
Gui
Widgets
Concurrent
)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Concurrent)
qt_standard_project_setup()
# Include cmake modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# Collect sources
add_subdirectory(src)
# ---------------------------------------------------------------------------
# Übersetzungen lrelease .qm ins Build-Verzeichnis
# ---------------------------------------------------------------------------
find_program(LRELEASE_EXECUTABLE
NAMES lrelease lrelease-qt6
HINTS "${Qt6_DIR}/../../../bin"
REQUIRED
)
set(TS_FILES
${CMAKE_CURRENT_SOURCE_DIR}/translations/barecode_de.ts
${CMAKE_CURRENT_SOURCE_DIR}/translations/barecode_en.ts
)
set(QM_FILES)
foreach(TS_FILE ${TS_FILES})
get_filename_component(TS_NAME ${TS_FILE} NAME_WE)
set(QM_FILE "${CMAKE_CURRENT_BINARY_DIR}/translations/${TS_NAME}.qm")
add_custom_command(
OUTPUT "${QM_FILE}"
COMMAND ${CMAKE_COMMAND} -E make_directory
"${CMAKE_CURRENT_BINARY_DIR}/translations"
COMMAND ${LRELEASE_EXECUTABLE} "${TS_FILE}" -qm "${QM_FILE}"
DEPENDS "${TS_FILE}"
COMMENT "lrelease: ${TS_NAME}.qm"
VERBATIM
)
list(APPEND QM_FILES "${QM_FILE}")
endforeach()
add_custom_target(BareCode_translations ALL DEPENDS ${QM_FILES})
# ---------------------------------------------------------------------------
# Resources
# ---------------------------------------------------------------------------
qt_add_resources(BARECODE_RESOURCES resources/resources.qrc)
# ---------------------------------------------------------------------------
# Executable
# Unter Windows: .rc-Datei für EXE-Icon einbinden
# ---------------------------------------------------------------------------
if(PLATFORM_WINDOWS)
qt_add_executable(BareCode
main.cpp
BareCode.rc
${BARECODE_RESOURCES}
)
qt_add_executable(BareCode main.cpp BareCode.rc ${BARECODE_RESOURCES})
else()
qt_add_executable(BareCode
main.cpp
${BARECODE_RESOURCES}
)
qt_add_executable(BareCode main.cpp ${BARECODE_RESOURCES})
endif()
add_dependencies(BareCode BareCode_translations)
target_link_libraries(BareCode PRIVATE
BareCode_Core
BareCode_Editor
@@ -95,7 +112,15 @@ install(TARGETS BareCode
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# ---- Linux (FreeDesktop) --------------------------------------------------
install(FILES LICENSE
DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/barecode
)
# .qm-Dateien installieren
install(FILES ${QM_FILES}
DESTINATION ${CMAKE_INSTALL_DATADIR}/BareCode/translations
)
if(PLATFORM_LINUX)
foreach(SIZE 16 32 48 64 128 256 512)
install(FILES resources/icon_${SIZE}.png
@@ -103,35 +128,27 @@ if(PLATFORM_LINUX)
RENAME barecode.png
)
endforeach()
install(FILES BareCode.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
)
endif()
# ---- Haiku ----------------------------------------------------------------
# Haiku verwendet kein FreeDesktop-System. Icons und MIME-Typen werden
# nativ über 'mimeset' gesetzt. Die PNG-Icons legen wir in den
# Haiku-typischen Pfad, ein post-install Skript ruft mimeset auf.
if(PLATFORM_HAIKU)
install(FILES resources/icon_256.png
DESTINATION ${CMAKE_INSTALL_DATADIR}/BareCode
RENAME BareCode.png
)
# mimeset nach der Installation ausführen um MIME-Typ zu registrieren
install(CODE "
execute_process(
COMMAND mimeset -f \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/BareCode\"
RESULT_VARIABLE _mimeset_result
)
if(NOT _mimeset_result EQUAL 0)
message(WARNING \"mimeset konnte nicht ausgeführt werden MIME-Typ muss manuell gesetzt werden.\")
message(WARNING \"mimeset konnte nicht ausgeführt werden.\")
endif()
")
endif()
# ---- macOS ----------------------------------------------------------------
if(PLATFORM_MACOS)
set_target_properties(BareCode PROPERTIES
MACOSX_BUNDLE TRUE
@@ -140,4 +157,3 @@ if(PLATFORM_MACOS)
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
)
endif()

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Dany Thinnes Projekt Hirnfrei
Copyright (c) 2025 Dany Thinnes / Projekt Hirnfrei
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

36
barecode/PKGBUILD Normal file
View File

@@ -0,0 +1,36 @@
# Maintainer: Dany Thinnes <dany@projekt-hirnfrei.de>
# Projekt Hirnfrei - https://www.projekt-hirnfrei.de
pkgname=barecode
pkgver=1.2.0
pkgrel=1
pkgdesc="Schlanker modularer Code-Editor für HTML, PHP, CSS und C++"
arch=('x86_64' 'aarch64')
url="https://www.projekt-hirnfrei.de"
license=('MIT')
depends=('qt6-base')
makedepends=('cmake' 'ninja')
provides=('barecode')
conflicts=('barecode-git')
source=("$pkgname-$pkgver.tar.gz::https://git.projekt-hirnfrei.de/diabolus/BareCode/archive/v$pkgver.tar.gz")
sha256sums=('SKIP') # Nach erstem Download ersetzen: sha256sum barecode-1.1.0.tar.gz
build() {
cd "BareCode"
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_SKIP_RPATH=ON
cmake --build build
}
check() {
:
}
package() {
cd "BareCode"
DESTDIR="$pkgdir" cmake --install build
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

43
barecode/PKGBUILD-git Normal file
View File

@@ -0,0 +1,43 @@
# Maintainer: Dany Thinnes <dany@projekt-hirnfrei.de>
# Projekt Hirnfrei - https://www.projekt-hirnfrei.de
pkgname=barecode-git
pkgver=1.1.0.r0.g0000000
pkgrel=1
pkgdesc="Schlanker modularer Code-Editor für HTML, PHP, CSS und C++ (Git-Version)"
arch=('x86_64' 'aarch64')
url="https://www.projekt-hirnfrei.de"
license=('MIT')
depends=('qt6-base')
makedepends=('cmake' 'ninja' 'git')
provides=('barecode')
conflicts=('barecode')
source=("$pkgname::git+https://git.projekt-hirnfrei.de/diabolus/BareCode.git")
sha256sums=('SKIP')
pkgver() {
cd "$pkgname"
git describe --long --tags 2>/dev/null \
| sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' \
|| printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build() {
cd "$pkgname"
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_SKIP_RPATH=ON
cmake --build build
}
check() {
:
}
package() {
cd "$pkgname"
DESTDIR="$pkgdir" cmake --install build
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

200
barecode/README.md Normal file
View File

@@ -0,0 +1,200 @@
# BareCode
**Schlanker, modularer Code-Editor für Web-Entwickler**
Version 1.2.0 — von **Dany Thinnes** | [Projekt Hirnfrei](https://www.projekt-hirnfrei.de) | [Discord](https://discord.projekt-hirnfrei.de)
Entwickelt mit **C++17**, **Qt6** und **CMake**.
Kompiliert auf **Linux**, **Windows** und **Haiku**.
---
## Installation
### Arch Linux / Manjaro (AUR)
```bash
# Mit yay
yay -S barecode
# Mit paru
paru -S barecode
# Immer aktuellster Git-Stand
yay -S barecode-git
```
### Aus dem Quellcode bauen
Siehe Abschnitt **Bauen** weiter unten.
---
## Features
### Editor
| Feature | Details |
|---|---|
| Syntax-Highlighting | HTML, PHP, CSS, C/C++ — inkl. mehrzeiliger Kommentare über Leerzeilen |
| Tabs | Mehrere Dateien gleichzeitig, verschiebbar und schließbar |
| Änderungsindikator | ● im Tab-Titel bei ungespeicherten Änderungen |
| Zeilennummern | Eigener Gutter, aktuelle Zeile hervorgehoben |
| Einrück-Führungslinien | Vertikale Linien wie in VS Code |
| Klammerzugehörigkeit | Zusammengehörige Klammern werden grün hervorgehoben, fehlende rot |
| Auto-Indent | Einrückungstiefe wird bei Enter übernommen |
| Tab / Shift+Tab | Ein- und Ausrücken für einzelne Zeilen und Selektionen |
| Smart Backspace | Springt zur vorherigen Tab-Stop-Position |
| Farbvorschau | CSS-Farbwerte (#hex, rgb, hsl) werden inline angezeigt |
| Farbauswahl | Klick auf das Farbquadrat öffnet einen Farbauswahl-Dialog |
| Funktionssignatur | Tooltip mit Parametern beim Tippen von PHP-Funktionen |
| Variablen-Popup | Vorschläge für $variablen beim Tippen (nur Anzeige, kein Autocomplete) |
### Navigation
| Feature | Details |
|---|---|
| Projektbaum | Linkes Panel — zeigt nur das gewählte Projektverzeichnis |
| Neue Datei / Ordner | Rechtsklick im Dateibaum |
| Doppelklick-Navigation | Doppelklick auf einen Funktionsnamen springt zur Definition |
| Suchen & Ersetzen | Einzeln, Alle, In Auswahl, Regex, Live-Hervorhebung (Strg+F) |
| In Dateien suchen | Rekursive Projektsuche mit Ergebnisliste (Strg+Shift+F) |
### Projektanalyse
| Feature | Details |
|---|---|
| Projektfunktionen | Alle definierten Funktionen als durchsuchbare Liste (Strg+Shift+P) |
| Tote Funktionen | Analyse ungenutzter Funktionen mit Exclude-Verzeichnissen (Strg+Shift+T) |
| Export | Analyseergebnisse als TXT exportieren |
### Allgemein
| Feature | Details |
|---|---|
| Speichern | Speichern, Speichern unter, Alles speichern |
| Dark / Light Mode | Umschaltbar (Strg+Shift+D), wird gespeichert |
| Session | Geöffnete Dateien und aktiver Tab werden wiederhergestellt |
---
## Tastaturkürzel
| Aktion | Kürzel |
|---|---|
| Datei öffnen | Strg+O |
| Neue Datei | Strg+N |
| Projekt öffnen | Strg+Shift+O |
| Speichern | Strg+S |
| Speichern unter | Strg+Shift+A |
| Alles speichern | Strg+Shift+S |
| Suchen / Ersetzen | Strg+F |
| In Dateien suchen | Strg+Shift+F |
| Projektfunktionen | Strg+Shift+P |
| Tote Funktionen | Strg+Shift+T |
| Dark Mode | Strg+Shift+D |
---
## Projektstruktur
```
BareCode/
├── CMakeLists.txt
├── main.cpp
├── LICENSE
├── PKGBUILD # Stabiles AUR-Paket
├── PKGBUILD-git # AUR-Paket (Git-Stand)
├── BareCode.desktop # Linux-Anwendungsmenü
├── resources/
│ ├── resources.qrc
│ ├── icon_*.png
│ └── php_functions.json
└── src/
├── core/ # MainWindow, ProjectManager, Settings,
│ # ThemeManager, AboutDialog, IPlugin
├── editor/ # EditorPanel, EditorTab, CodeEditor,
│ # SearchPanel, FileSearchPanel,
│ # ColorIndicator, SignatureHelper,
│ # VariableCompleter, FunctionScanner,
│ # FunctionIndex, FunctionListPanel,
│ # FunctionListDialog, DeadCodeAnalyzer,
│ # DeadCodeDialog
├── filetree/ # FileTreePanel
└── highlighter/ # SyntaxHighlighter, HighlighterFactory
```
Jedes Unterverzeichnis kompiliert als eigene statische Bibliothek.
---
## Abhängigkeiten & Bauen
### Linux
**Ubuntu / Debian:**
```bash
sudo apt install cmake ninja-build qt6-base-dev qt6-base-dev-tools
```
**Arch / Manjaro:**
```bash
sudo pacman -S cmake ninja qt6-base
```
**Bauen:**
```bash
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/BareCode
```
**Installieren:**
```bash
sudo cmake --install build
```
---
### Windows
**Abhängigkeiten:**
- [Qt6](https://www.qt.io/download) — Komponenten: Qt6 Base, Qt6 Concurrent
- CMake ≥ 3.16
- Visual Studio 2019 oder neuer
```bat
cmake -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
```
---
### Haiku
```bash
pkgman install qt6_base qt6_base_devel
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
./build/BareCode
```
> Getestet auf Haiku R1/beta5.
---
## Neue Sprache hinzufügen
1. Subklasse von `SyntaxHighlighter` in `src/highlighter/` erstellen
2. `m_rules` im Konstruktor befüllen (siehe `CppHighlighter` als Vorlage)
3. Dateiendung in `HighlighterFactory.cpp` registrieren
---
## Lizenz
MIT License — Copyright (c) 2025 Dany Thinnes / Projekt Hirnfrei
---
## Links
- Website: [www.projekt-hirnfrei.de](https://www.projekt-hirnfrei.de)
- Discord: [discord.projekt-hirnfrei.de](https://discord.projekt-hirnfrei.de)
- Quellcode: [git.projekt-hirnfrei.de/diabolus/BareCode](https://git.projekt-hirnfrei.de/diabolus/BareCode)
- AUR: [aur.archlinux.org/packages/barecode](https://aur.archlinux.org/packages/barecode)

133
barecode/bauen.sh Executable file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# ---------------------------------------------------------------------------
# BareCode Build-Skript
# Verwendung: ./bauen.sh [release|debug|clean|install]
# ---------------------------------------------------------------------------
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
BUILD_TYPE="${1:-release}"
BUILD_DIR="build"
# Farben
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}==>${NC} $1"; }
success() { echo -e "${GREEN}==>${NC} $1"; }
warn() { echo -e "${YELLOW}==>${NC} $1"; }
error() { echo -e "${RED}==>${NC} $1"; exit 1; }
# ---------------------------------------------------------------------------
# clean
# ---------------------------------------------------------------------------
if [ "$BUILD_TYPE" = "clean" ]; then
info "Build-Verzeichnis wird gelöscht…"
rm -rf "$BUILD_DIR"
success "Fertig."
exit 0
fi
# ---------------------------------------------------------------------------
# Abhängigkeiten prüfen
# ---------------------------------------------------------------------------
info "Prüfe Abhängigkeiten…"
command -v cmake &>/dev/null || error "cmake nicht gefunden. Installation: sudo pacman -S cmake"
command -v ninja &>/dev/null || error "ninja nicht gefunden. Installation: sudo pacman -S ninja"
command -v lrelease &>/dev/null || \
command -v lrelease-qt6 &>/dev/null || \
error "lrelease nicht gefunden. Installation: sudo pacman -S qt6-tools"
# Qt6 prüfen
if ! pkg-config --exists Qt6Core 2>/dev/null; then
if ! cmake --find-package -DNAME=Qt6 -DCOMPILER_ID=GNU \
-DLANGUAGE=CXX -DMODE=EXIST &>/dev/null 2>&1; then
warn "Qt6 konnte nicht automatisch geprüft werden — cmake wird es versuchen."
fi
fi
success "Abhängigkeiten OK."
# ---------------------------------------------------------------------------
# CMake-Build-Typ
# ---------------------------------------------------------------------------
case "$BUILD_TYPE" in
release|Release)
CMAKE_BUILD_TYPE="Release"
;;
debug|Debug)
CMAKE_BUILD_TYPE="Debug"
;;
install)
CMAKE_BUILD_TYPE="Release"
;;
*)
error "Unbekannter Build-Typ: '$BUILD_TYPE'. Erlaubt: release, debug, clean, install"
;;
esac
# ---------------------------------------------------------------------------
# Konfigurieren
# ---------------------------------------------------------------------------
info "Konfiguriere mit CMake (${CMAKE_BUILD_TYPE})…"
cmake -B "$BUILD_DIR" \
-G Ninja \
-DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
2>&1 || error "CMake-Konfiguration fehlgeschlagen."
success "Konfiguration OK."
# ---------------------------------------------------------------------------
# Bauen
# ---------------------------------------------------------------------------
JOBS=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)
info "Baue mit $JOBS parallelen Jobs…"
cmake --build "$BUILD_DIR" -j"$JOBS" 2>&1 || error "Build fehlgeschlagen."
success "Build erfolgreich!"
# ---------------------------------------------------------------------------
# Installieren (optional)
# ---------------------------------------------------------------------------
if [ "$BUILD_TYPE" = "install" ]; then
info "Installiere nach /usr (sudo erforderlich)…"
sudo cmake --install "$BUILD_DIR" || error "Installation fehlgeschlagen."
success "BareCode wurde installiert."
echo ""
echo " Starten: BareCode"
exit 0
fi
# ---------------------------------------------------------------------------
# Fertig
# ---------------------------------------------------------------------------
BINARY="$BUILD_DIR/BareCode"
if [ -f "$BINARY" ]; then
# .qm-Dateien neben die Binary kopieren damit sie gefunden werden
mkdir -p "$BUILD_DIR/translations"
if compgen -G "$BUILD_DIR/translations/barecode_*.qm" > /dev/null 2>&1; then
success "Übersetzungen bereits vorhanden."
else
# Aus dem cmake-Zwischenverzeichnis holen
find "$BUILD_DIR" -name "barecode_*.qm" -not -path "$BUILD_DIR/translations/*" \
-exec cp {} "$BUILD_DIR/translations/" \; 2>/dev/null || true
fi
echo ""
success "BareCode ist bereit:"
echo " Starten: ./$BINARY"
echo " Installieren: ./bauen.sh install"
echo " Debug-Build: ./bauen.sh debug"
echo " Aufräumen: ./bauen.sh clean"
fi

106
barecode/main.cpp Normal file
View File

@@ -0,0 +1,106 @@
#include <QApplication>
#include <QIcon>
#include <QTranslator>
#include <QLocale>
#include <QLibraryInfo>
#include <QSettings>
#include <QDir>
#include <QCoreApplication>
#include "core/MainWindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setApplicationName("BareCode");
app.setApplicationVersion("1.2.0");
app.setOrganizationName("BareCode");
// Icon
QIcon appIcon;
appIcon.addFile(":/icon_16.png", QSize(16, 16));
appIcon.addFile(":/icon_32.png", QSize(32, 32));
appIcon.addFile(":/icon_48.png", QSize(48, 48));
appIcon.addFile(":/icon_64.png", QSize(64, 64));
appIcon.addFile(":/icon_128.png", QSize(128, 128));
appIcon.addFile(":/icon_256.png", QSize(256, 256));
appIcon.addFile(":/icon_512.png", QSize(512, 512));
app.setWindowIcon(appIcon);
// ---------------------------------------------------------------------------
// Übersetzung laden
// .qm-Dateien liegen im Unterverzeichnis "translations" neben der Binary
// oder unter /usr/share/BareCode/translations nach Installation
// ---------------------------------------------------------------------------
QSettings settings(QSettings::IniFormat, QSettings::UserScope,
"BareCode", "BareCode");
QString locale = settings.value("language/locale", QString()).toString();
if (locale.isEmpty())
{
locale = QLocale::system().name(); // z.B. "de_DE", "en_US"
}
const QString shortLocale = locale.left(2); // "de", "en"
// Suchpfade für .qm-Dateien
QStringList searchPaths;
searchPaths << QCoreApplication::applicationDirPath() + "/translations"
<< QDir::homePath() + "/.local/share/BareCode/translations"
<< "/usr/share/BareCode/translations"
<< "/usr/local/share/BareCode/translations";
// Qt-eigene Übersetzungen (Dialoge, Standard-Buttons)
QTranslator qtTranslator;
if (qtTranslator.load("qt_" + locale,
QLibraryInfo::path(QLibraryInfo::TranslationsPath)))
{
app.installTranslator(&qtTranslator);
}
// BareCode-Übersetzung — in allen Suchpfaden versuchen
QTranslator appTranslator;
bool loaded = false;
for (const QString &path : searchPaths)
{
if (appTranslator.load(QString("barecode_%1").arg(locale), path) ||
appTranslator.load(QString("barecode_%1").arg(shortLocale), path))
{
app.installTranslator(&appTranslator);
loaded = true;
break;
}
}
// Fallback: Englisch
if (!loaded && shortLocale != "de")
{
for (const QString &path : searchPaths)
{
if (appTranslator.load("barecode_en", path))
{
app.installTranslator(&appTranslator);
break;
}
}
}
MainWindow window;
window.show();
// Datei(en) öffnen, die beim Start per Kommandozeile übergeben wurden
// (z. B. Doppelklick auf eine Datei im Dateibrowser → "Öffnen mit BareCode").
// Das erste Argument ist der Programmname selbst und wird übersprungen.
QStringList cliFiles = app.arguments();
if (!cliFiles.isEmpty())
{
cliFiles.removeFirst();
}
if (!cliFiles.isEmpty())
{
window.openFilesFromArguments(cliFiles);
}
return app.exec();
}

View File

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 824 B

After

Width:  |  Height:  |  Size: 824 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 303 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -74,7 +74,7 @@ AboutDialog::AboutDialog(QWidget *parent)
info->addLayout(row);
};
makeRow(tr("Version"), "1.1.0");
makeRow(tr("Version"), "1.2.0");
makeRow(tr("Entwickler"), "Dany Thinnes");
makeRow(tr("Projekt"), "Projekt Hirnfrei");
makeRow(tr("Framework"), QString("Qt %1").arg(qVersion()));

View File

@@ -5,6 +5,7 @@
#include <QMessageBox>
#include <QCloseEvent>
#include <QSettings>
#include <QFileInfo>
#include "AboutDialog.h"
#include "filetree/FileTreePanel.h"
@@ -45,6 +46,21 @@ MainWindow::MainWindow(QWidget *parent)
MainWindow::~MainWindow() = default;
// ---------------------------------------------------------------------------
// Datei(en) von der Kommandozeile öffnen
// ---------------------------------------------------------------------------
void MainWindow::openFilesFromArguments(const QStringList &filePaths)
{
for (const QString &path : filePaths)
{
const QFileInfo info(path);
if (info.exists() && info.isFile())
{
m_editor->openFile(info.absoluteFilePath());
}
}
}
// ---------------------------------------------------------------------------
// UI aufbauen
// ---------------------------------------------------------------------------
@@ -119,6 +135,14 @@ void MainWindow::setupMenuBar()
m_actFileSearch = mBearbeiten->addAction(tr("In &Dateien suchen…"), this, &MainWindow::onShowFileSearch);
m_actFileSearch->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_F);
mBearbeiten->addSeparator();
m_actFuncList = mBearbeiten->addAction(tr("&Projektfunktionen…"), this, &MainWindow::onShowFunctionList);
m_actFuncList->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
m_actDeadCode = mBearbeiten->addAction(tr("&Tote Funktionen suchen…"), this, &MainWindow::onShowDeadCode);
m_actDeadCode->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_T);
// ---- Ansicht ----
QMenu *mAnsicht = menuBar()->addMenu(tr("&Ansicht"));
@@ -127,6 +151,37 @@ void MainWindow::setupMenuBar()
m_actDarkMode->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_D);
connect(m_actDarkMode, &QAction::toggled, this, &MainWindow::onToggleDarkMode);
mAnsicht->addSeparator();
// Sprachauswahl
QMenu *mSprache = mAnsicht->addMenu(tr("Sprache"));
m_langGroup = new QActionGroup(this);
m_langGroup->setExclusive(true);
const QSettings langSettings(QSettings::IniFormat, QSettings::UserScope,
"BareCode", "BareCode");
const QString currentLocale = langSettings.value("language/locale",
QLocale::system().name()).toString();
struct LangEntry { QString locale; QString label; };
const QList<LangEntry> languages = {
{ "de", "Deutsch" },
{ "en", "English" },
};
for (const LangEntry &lang : languages)
{
QAction *act = mSprache->addAction(lang.label);
act->setCheckable(true);
act->setData(lang.locale);
act->setChecked(currentLocale.startsWith(lang.locale));
m_langGroup->addAction(act);
connect(act, &QAction::triggered, this, [this, lang]()
{
onLanguageChanged(lang.locale);
});
}
// ---- Hilfe ----
QMenu *mHilfe = menuBar()->addMenu(tr("&Hilfe"));
m_actAbout = mHilfe->addAction(tr("&Über BareCode…"), this, &MainWindow::onAbout);
@@ -269,6 +324,17 @@ void MainWindow::onToggleDarkMode(bool checked)
m_settings->setDarkMode(checked);
}
void MainWindow::onLanguageChanged(const QString &locale)
{
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
s.setValue("language/locale", locale);
QMessageBox::information(this,
tr("Sprache geändert"),
tr("Die Sprache wird beim nächsten Start von BareCode aktiv.")
);
}
// ---------------------------------------------------------------------------
// Slots Hilfe
// ---------------------------------------------------------------------------
@@ -281,6 +347,16 @@ void MainWindow::onAbout()
// ---------------------------------------------------------------------------
// Slots Projekt
// ---------------------------------------------------------------------------
void MainWindow::onShowDeadCode()
{
m_editor->showDeadCode();
}
void MainWindow::onShowFunctionList()
{
m_editor->showFunctionList();
}
void MainWindow::onShowFileSearch()
{
m_editor->showFileSearchPanel();

View File

@@ -5,6 +5,7 @@
#include <QMenuBar>
#include <QStatusBar>
#include <QAction>
#include <QActionGroup>
#include <memory>
#include "ProjectManager.h"
@@ -25,6 +26,11 @@ public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
// Datei(en) öffnen, die beim Programmstart per Kommandozeile übergeben
// wurden — z. B. durch Doppelklick auf eine Datei im Dateibrowser
// ("Öffnen mit BareCode").
void openFilesFromArguments(const QStringList &filePaths);
protected:
void closeEvent(QCloseEvent *event) override;
@@ -42,8 +48,11 @@ private slots:
void onRedo();
void onShowSearch();
void onShowFileSearch();
void onShowFunctionList();
void onShowDeadCode();
// Ansicht
void onToggleDarkMode(bool checked);
void onLanguageChanged(const QString &locale);
// Hilfe
void onAbout();
// Intern
@@ -82,6 +91,9 @@ private:
QAction *m_actRedo = nullptr;
QAction *m_actSearch = nullptr;
QAction *m_actFileSearch = nullptr;
QAction *m_actFuncList = nullptr;
QAction *m_actDeadCode = nullptr;
QAction *m_actDarkMode = nullptr;
QActionGroup *m_langGroup = nullptr;
QAction *m_actAbout = nullptr;
};

View File

@@ -17,6 +17,20 @@ set(EDITOR_SOURCES
SignatureHelper.h
SignatureTooltip.cpp
SignatureTooltip.h
FunctionScanner.cpp
FunctionScanner.h
FunctionIndex.cpp
FunctionIndex.h
FunctionListPanel.cpp
FunctionListPanel.h
FunctionListDialog.cpp
FunctionListDialog.h
DeadCodeAnalyzer.cpp
DeadCodeAnalyzer.h
DeadCodeDialog.cpp
DeadCodeDialog.h
VariableCompleter.cpp
VariableCompleter.h
)
add_library(BareCode_Editor STATIC ${EDITOR_SOURCES})

View File

@@ -2,6 +2,8 @@
#include "LineNumberArea.h"
#include "ColorIndicator.h"
#include "SignatureHelper.h"
#include "FunctionIndex.h"
#include "VariableCompleter.h"
#include "core/Settings.h"
#include "highlighter/HighlighterFactory.h"
@@ -12,6 +14,7 @@
#include <QResizeEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QFocusEvent>
#include <QScrollBar>
#include <QFile>
#include <QTextStream>
@@ -26,6 +29,7 @@ CodeEditor::CodeEditor(Settings *settings, QWidget *parent)
m_lineNumberArea = new LineNumberArea(this);
m_colorIndicator = new ColorIndicator(this);
m_signatureHelper = new SignatureHelper(this);
m_varCompleter = new VariableCompleter(this);
setupEditor();
connect(this, &CodeEditor::blockCountChanged,
@@ -289,6 +293,44 @@ void CodeEditor::paintEvent(QPaintEvent *event)
}
}
void CodeEditor::setFunctionIndex(FunctionIndex *index)
{
m_functionIndex = index;
}
void CodeEditor::mouseDoubleClickEvent(QMouseEvent *event)
{
// Zuerst normales Verhalten — markiert das Wort unter dem Cursor
QPlainTextEdit::mouseDoubleClickEvent(event);
if (!m_functionIndex || !m_functionIndex->isReady())
{
return;
}
// Markiertes Wort auslesen
const QString word = textCursor().selectedText().trimmed();
if (word.isEmpty() || word.contains(' '))
{
return;
}
// Im Funktionsindex nachschlagen
const FunctionScanner::FunctionInfo info = m_functionIndex->lookup(word);
if (info.filePath.isEmpty())
{
return; // Nicht gefunden — normales Verhalten bleibt
}
// Nicht zur eigenen Definition springen wenn wir bereits dort sind
if (info.filePath == m_filePath && info.line == textCursor().blockNumber() + 1)
{
return;
}
emit navigateToRequested(info.filePath, info.line);
}
void CodeEditor::mousePressEvent(QMouseEvent *event)
{
// Zuerst prüfen ob ein Farbquadrat geklickt wurde
@@ -351,7 +393,7 @@ void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
}
// ---------------------------------------------------------------------------
// Current line highlight
// Current line highlight + Klammerzugehörigkeit
// ---------------------------------------------------------------------------
void CodeEditor::highlightCurrentLine()
{
@@ -359,20 +401,161 @@ void CodeEditor::highlightCurrentLine()
if (!isReadOnly())
{
QTextEdit::ExtraSelection selection;
// Aktuelle Zeile hervorheben
QTextEdit::ExtraSelection lineSelection;
lineSelection.format.setBackground(palette().color(QPalette::AlternateBase));
lineSelection.format.setProperty(QTextFormat::FullWidthSelection, true);
lineSelection.cursor = textCursor();
lineSelection.cursor.clearSelection();
extraSelections.append(lineSelection);
const QColor lineColor = palette().color(QPalette::AlternateBase);
selection.format.setBackground(lineColor);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
// Klammerzugehörigkeit
matchBrackets(extraSelections);
}
setExtraSelections(extraSelections);
}
void CodeEditor::matchBrackets(QList<QTextEdit::ExtraSelection> &selections)
{
static const QString openBrackets = "({[";
static const QString closeBrackets = ")}]";
QTextCursor cursor = textCursor();
const QString blockText = cursor.block().text();
const int col = cursor.columnNumber();
// Zeichen unter oder links vom Cursor prüfen
QChar ch;
int charPos = -1;
// Zuerst Zeichen unter dem Cursor
if (col < blockText.length())
{
ch = blockText[col];
if (openBrackets.contains(ch) || closeBrackets.contains(ch))
{
charPos = col;
}
}
// Dann Zeichen links vom Cursor
if (charPos == -1 && col > 0)
{
ch = blockText[col - 1];
if (openBrackets.contains(ch) || closeBrackets.contains(ch))
{
charPos = col - 1;
}
}
if (charPos == -1)
{
return;
}
// Passende Klammer suchen
const bool isOpen = openBrackets.contains(ch);
const int bracketIndex = isOpen
? openBrackets.indexOf(ch)
: closeBrackets.indexOf(ch);
const QChar matchChar = isOpen
? closeBrackets[bracketIndex]
: openBrackets[bracketIndex];
// Dokumentposition der gefundenen Klammer
const int startPos = cursor.block().position() + charPos;
// Passende Klammer suchen — vorwärts oder rückwärts
int depth = 0;
int matchPos = -1;
if (isOpen)
{
// Vorwärts suchen
QTextCursor search(document());
search.setPosition(startPos);
while (!search.atEnd())
{
search.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
const QChar c = search.selectedText()[0];
search.clearSelection();
if (c == ch) { ++depth; }
else if (c == matchChar)
{
--depth;
if (depth == 0)
{
matchPos = search.position() - 1;
break;
}
}
}
}
else
{
// Rückwärts suchen
QTextCursor search(document());
search.setPosition(startPos + 1);
while (search.position() > 0)
{
search.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
const QChar c = search.selectedText()[0];
search.clearSelection();
if (c == ch) { ++depth; }
else if (c == matchChar)
{
--depth;
if (depth == 0)
{
matchPos = search.position();
break;
}
}
}
}
// Formatierung
QTextCharFormat matchFormat;
if (matchPos >= 0)
{
// Gefunden — beide Klammern grün hervorheben
matchFormat.setBackground(QColor("#1a5a1a"));
matchFormat.setForeground(QColor("#88ff88"));
matchFormat.setFontWeight(QFont::Bold);
}
else
{
// Kein Match — rot markieren
matchFormat.setBackground(QColor("#5a1a1a"));
matchFormat.setForeground(QColor("#ff8888"));
matchFormat.setFontWeight(QFont::Bold);
}
// Öffnende / schließende Klammer markieren
QTextEdit::ExtraSelection sel1;
sel1.cursor = QTextCursor(document());
sel1.cursor.setPosition(startPos);
sel1.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
sel1.format = matchFormat;
selections.append(sel1);
// Passende Klammer markieren (nur wenn gefunden)
if (matchPos >= 0)
{
QTextEdit::ExtraSelection sel2;
sel2.cursor = QTextCursor(document());
sel2.cursor.setPosition(matchPos);
sel2.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
sel2.format = matchFormat;
selections.append(sel2);
}
}
// ---------------------------------------------------------------------------
// Key handling Tab/Shift+Tab, Smart-Backspace, Auto-Indent
// ---------------------------------------------------------------------------
@@ -391,6 +574,16 @@ void CodeEditor::keyPressEvent(QKeyEvent *event)
QTextBlock startBlock = document()->findBlock(cursor.selectionStart());
QTextBlock endBlock = document()->findBlock(cursor.selectionEnd());
// Wenn die Selektion genau am Anfang des letzten Blocks endet,
// diesen Block nicht mit einbeziehen — der Cursor steht dort nur
// mit Position 0, der Nutzer hat die Zeile nicht markiert
if (cursor.hasSelection() &&
cursor.selectionEnd() == endBlock.position() &&
endBlock != startBlock)
{
endBlock = endBlock.previous();
}
cursor.beginEditBlock();
for (QTextBlock b = startBlock; b != endBlock.next(); b = b.next())
{
@@ -399,22 +592,14 @@ void CodeEditor::keyPressEvent(QKeyEvent *event)
if (m_settings->useSpacesForTabs())
{
// Bis zu tabSize führende Leerzeichen entfernen
for (int i = 0; i < tabSize && i < lineText.length(); ++i)
{
if (lineText[i] == ' ')
{
++toRemove;
}
else
{
break;
}
if (lineText[i] == ' ') { ++toRemove; }
else { break; }
}
}
else
{
// Einen führenden Tab entfernen
if (!lineText.isEmpty() && lineText[0] == '\t')
{
toRemove = 1;
@@ -444,10 +629,16 @@ void CodeEditor::keyPressEvent(QKeyEvent *event)
if (cursor.hasSelection())
{
// Mehrere Zeilen einrücken
QTextBlock startBlock = document()->findBlock(cursor.selectionStart());
QTextBlock endBlock = document()->findBlock(cursor.selectionEnd());
// Gleiche Korrektur: Cursor am Zeilenanfang → Zeile nicht einrücken
if (cursor.selectionEnd() == endBlock.position() &&
endBlock != startBlock)
{
endBlock = endBlock.previous();
}
cursor.beginEditBlock();
for (QTextBlock b = startBlock; b != endBlock.next(); b = b.next())
{
@@ -552,5 +743,20 @@ void CodeEditor::keyPressEvent(QKeyEvent *event)
}
QPlainTextEdit::keyPressEvent(event);
// Variablen-Popup nach jedem Tastendruck aktualisieren
m_varCompleter->handleKeyPress(event);
}
// ---------------------------------------------------------------------------
// Fokus verloren — z. B. beim Wechsel zu einem anderen Tab.
// Ein noch offenes Variablen-Popup muss hier geschlossen werden, sonst
// bleibt es als verwaistes Fenster stehen und kann bei mehreren offenen
// Dateien den Fokus blockieren.
// ---------------------------------------------------------------------------
void CodeEditor::focusOutEvent(QFocusEvent *event)
{
QPlainTextEdit::focusOutEvent(event);
m_varCompleter->notifyFocusLost();
}

View File

@@ -10,6 +10,8 @@ class Settings;
class SyntaxHighlighter;
class ColorIndicator;
class SignatureHelper;
class FunctionIndex;
class VariableCompleter;
// ---------------------------------------------------------------------------
// CodeEditor Core editing widget.
@@ -48,14 +50,19 @@ public:
QRectF blockBoundingGeometryPublic(const QTextBlock &b) const { return blockBoundingGeometry(b); }
QPointF contentOffsetPublic() const { return contentOffset(); }
void setFunctionIndex(FunctionIndex *index);
signals:
void fileSaved(const QString &filePath);
void navigateToRequested(const QString &filePath, int line);
protected:
void resizeEvent(QResizeEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
void focusOutEvent(QFocusEvent *event) override;
private slots:
void updateLineNumberAreaWidth(int newBlockCount);
@@ -66,11 +73,14 @@ private:
void setupEditor();
void installHighlighter(const QString &filePath);
bool writeToFile(const QString &filePath);
void matchBrackets(QList<QTextEdit::ExtraSelection> &selections);
Settings *m_settings = nullptr;
LineNumberArea *m_lineNumberArea = nullptr;
SyntaxHighlighter *m_highlighter = nullptr;
ColorIndicator *m_colorIndicator = nullptr;
SignatureHelper *m_signatureHelper = nullptr;
FunctionIndex *m_functionIndex = nullptr;
VariableCompleter *m_varCompleter = nullptr;
QString m_filePath;
};

View File

@@ -0,0 +1,181 @@
#include "DeadCodeAnalyzer.h"
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
#include <QSet>
#include <QFileInfo>
DeadCodeAnalyzer::DeadCodeAnalyzer(QObject *parent)
: QObject(parent)
{
m_scanner = new FunctionScanner(this);
}
QList<DeadCodeAnalyzer::DeadFunction> DeadCodeAnalyzer::analyze(
const QString &projectRoot,
const QStringList &extensions,
const QStringList &excludeDirs) const
{
// -----------------------------------------------------------------------
// Schritt 1: Alle Dateien sammeln (mit Exclude-Filter)
// -----------------------------------------------------------------------
QStringList allFiles;
QDirIterator it(projectRoot, extensions, QDir::Files,
QDirIterator::Subdirectories);
while (it.hasNext())
{
const QString path = it.next();
// Exclude-Verzeichnisse prüfen
bool excluded = false;
if (!excludeDirs.isEmpty())
{
const QStringList parts = path.split('/');
for (const QString &excl : excludeDirs)
{
if (parts.contains(excl.trimmed(), Qt::CaseInsensitive))
{
excluded = true;
break;
}
}
}
if (!excluded)
{
allFiles.append(path);
}
}
const int totalFiles = allFiles.size();
// -----------------------------------------------------------------------
// Schritt 2: Alle Funktionsdefinitionen aus nicht-excluded Dateien
// -----------------------------------------------------------------------
QHash<QString, FunctionScanner::FunctionInfo> definitions;
for (const QString &path : allFiles)
{
const QList<FunctionScanner::FunctionInfo> funcs = m_scanner->scanFile(path);
for (const FunctionScanner::FunctionInfo &func : funcs)
{
if (func.name.startsWith("__"))
{
continue;
}
const QString key = func.name.toLower();
if (!definitions.contains(key))
{
definitions.insert(key, func);
}
}
}
if (definitions.isEmpty())
{
return {};
}
// -----------------------------------------------------------------------
// Schritt 3: Alle Dateien EINMAL lesen, alle Aufrufe sammeln
// -----------------------------------------------------------------------
QSet<QString> calledFunctions;
static const QRegularExpression identifierRegex(
R"((?:->|::|\b)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\s*\()"
R"(|['"]([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)['"])"
);
static const QRegularExpression defLineRegex(
R"(\bfunction\s+([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*))"
);
int filesScanned = 0;
for (const QString &filePath : allFiles)
{
++filesScanned;
// Fortschritt signalisieren
emit progressUpdate(filesScanned, totalFiles,
QFileInfo(filePath).fileName());
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
continue;
}
QTextStream stream(&file);
stream.setEncoding(QStringConverter::Utf8);
while (!stream.atEnd())
{
const QString line = stream.readLine();
const QString trimmed = line.trimmed();
if (trimmed.startsWith("//") ||
trimmed.startsWith("*") ||
trimmed.startsWith("#") ||
trimmed.startsWith("/*"))
{
continue;
}
// Definitionen auf dieser Zeile nicht als Aufruf werten
QSet<QString> definedOnThisLine;
{
QRegularExpressionMatchIterator dit = defLineRegex.globalMatch(line);
while (dit.hasNext())
{
definedOnThisLine.insert(dit.next().captured(1).toLower());
}
}
QRegularExpressionMatchIterator mit = identifierRegex.globalMatch(line);
while (mit.hasNext())
{
const QRegularExpressionMatch match = mit.next();
const QString name = match.captured(1).isEmpty()
? match.captured(2).toLower()
: match.captured(1).toLower();
if (name.isEmpty() || definedOnThisLine.contains(name))
{
continue;
}
if (definitions.contains(name))
{
calledFunctions.insert(name);
}
}
}
}
// -----------------------------------------------------------------------
// Schritt 4: Nicht aufgerufene Funktionen zurückgeben
// -----------------------------------------------------------------------
QList<DeadFunction> dead;
for (auto it2 = definitions.begin(); it2 != definitions.end(); ++it2)
{
if (!calledFunctions.contains(it2.key()))
{
const FunctionScanner::FunctionInfo &func = it2.value();
DeadFunction d;
d.info = func;
d.reason = func.className.isEmpty()
? QObject::tr("Globale Funktion — kein Aufruf gefunden")
: QObject::tr("Methode von %1 — kein Aufruf gefunden")
.arg(func.className);
dead.append(d);
}
}
return dead;
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <QObject>
#include <QString>
#include <QStringList>
#include <QList>
#include "FunctionScanner.h"
// ---------------------------------------------------------------------------
// DeadCodeAnalyzer Findet Funktionen die definiert aber nie aufgerufen werden.
// ---------------------------------------------------------------------------
class DeadCodeAnalyzer : public QObject
{
Q_OBJECT
public:
struct DeadFunction
{
FunctionScanner::FunctionInfo info;
QString reason;
};
explicit DeadCodeAnalyzer(QObject *parent = nullptr);
// excludeDirs: Verzeichnisnamen die komplett übersprungen werden
// z.B. {"vendor", "lib", "node_modules"}
QList<DeadFunction> analyze(const QString &projectRoot,
const QStringList &extensions = {"*.php"},
const QStringList &excludeDirs = {}) const;
signals:
// Fortschritt während der Analyse (läuft im Thread)
void progressUpdate(int filesScanned, int filesTotal,
const QString &currentFile) const;
private:
FunctionScanner *m_scanner = nullptr;
};

View File

@@ -0,0 +1,461 @@
#include "DeadCodeDialog.h"
#include <QtConcurrent/QtConcurrent>
#include <QFileInfo>
#include <QDir>
#include <QFont>
#include <QSettings>
#include <QCloseEvent>
#include <QFileDialog>
#include <QTextStream>
#include <QMessageBox>
#include <QHeaderView>
#include <QDateTime>
#include <QMetaObject>
DeadCodeDialog::DeadCodeDialog(QWidget *parent)
: QDialog(parent, Qt::Window)
{
setWindowTitle(tr("Tote Funktionen BareCode"));
setMinimumSize(650, 500);
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
if (s.contains("deadcode/geometry"))
{
restoreGeometry(s.value("deadcode/geometry").toByteArray());
}
else
{
resize(780, 620);
}
m_analyzer = new DeadCodeAnalyzer(this);
m_watcher = new QFutureWatcher<QList<DeadCodeAnalyzer::DeadFunction>>(this);
connect(m_watcher, &QFutureWatcher<QList<DeadCodeAnalyzer::DeadFunction>>::finished,
this, &DeadCodeDialog::onAnalysisFinished);
// Fortschritts-Signal aus dem Analyzer-Thread sicher in den UI-Thread leiten
connect(m_analyzer, &DeadCodeAnalyzer::progressUpdate,
this, &DeadCodeDialog::onProgressUpdate,
Qt::QueuedConnection);
setupUi();
}
// ---------------------------------------------------------------------------
// UI
// ---------------------------------------------------------------------------
void DeadCodeDialog::setupUi()
{
QVBoxLayout *root = new QVBoxLayout(this);
root->setSpacing(8);
root->setContentsMargins(12, 12, 12, 12);
// ---- Warnbox ----
QFrame *warningBox = new QFrame(this);
warningBox->setStyleSheet(
"QFrame {"
" background: #3a2a00;"
" border: 1px solid #7a5a00;"
" border-radius: 4px;"
"}"
);
QHBoxLayout *warnLayout = new QHBoxLayout(warningBox);
warnLayout->setContentsMargins(8, 6, 8, 6);
QLabel *warnIcon = new QLabel(warningBox);
warnIcon->setFixedSize(20, 20);
warnIcon->setStyleSheet(
"background: #ffcc00;"
"color: #1a1a00;"
"font-weight: bold;"
"font-size: 13px;"
"border-radius: 10px;"
);
warnIcon->setText("!");
warnIcon->setAlignment(Qt::AlignCenter);
warnLayout->addWidget(warnIcon);
QLabel *warnText = new QLabel(
tr("Kandidatenliste — kein Aufruf per Regex gefunden. "
"Dynamische Aufrufe (call_user_func, Strings, Hooks) werden nicht erkannt. "
"Bitte vor dem Löschen manuell prüfen."),
warningBox
);
warnText->setWordWrap(true);
warnText->setStyleSheet("color: #ffcc88;");
warnLayout->addWidget(warnText, 1);
root->addWidget(warningBox);
// ---- Exclude-Verzeichnisse ----
QHBoxLayout *excludeRow = new QHBoxLayout();
excludeRow->addWidget(new QLabel(tr("Ausschließen:"), this));
m_excludeEdit = new QLineEdit(this);
m_excludeEdit->setPlaceholderText(tr("vendor lib node_modules (leerzeichen-getrennt)"));
m_excludeEdit->setToolTip(tr(
"Verzeichnisnamen die bei der Analyse übersprungen werden.\n"
"Leerzeichen-getrennt, z.B.: vendor lib node_modules cache"
));
// Letzte Einstellung wiederherstellen
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
m_excludeEdit->setText(s.value("deadcode/excludeDirs", "vendor lib node_modules").toString());
excludeRow->addWidget(m_excludeEdit, 1);
// Verzeichnis-Auswahl Button
QPushButton *btnBrowse = new QPushButton(tr("+ Verzeichnis"), this);
btnBrowse->setToolTip(tr("Verzeichnis aus dem Projekt auswählen und zur Ausschlussliste hinzufügen"));
connect(btnBrowse, &QPushButton::clicked, this, [this]()
{
const QString startPath = m_projectRoot.isEmpty()
? QDir::homePath()
: m_projectRoot;
const QString dir = QFileDialog::getExistingDirectory(
this,
tr("Verzeichnis ausschließen"),
startPath,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks
);
if (dir.isEmpty())
{
return;
}
// Nur den letzten Verzeichnisnamen verwenden, nicht den vollen Pfad
// So funktioniert der Exclude auch für gleichnamige Unterverzeichnisse
const QString dirName = QFileInfo(dir).fileName();
// Prüfen ob bereits in der Liste
const QStringList current = m_excludeEdit->text()
.simplified()
.split(' ', Qt::SkipEmptyParts);
if (current.contains(dirName, Qt::CaseInsensitive))
{
return;
}
// Anhängen
QString newText = m_excludeEdit->text().trimmed();
if (!newText.isEmpty())
{
newText += ' ';
}
newText += dirName;
m_excludeEdit->setText(newText);
});
excludeRow->addWidget(btnBrowse);
// Löschen-Button — ganzes Feld leeren
QPushButton *btnClear = new QPushButton(tr(""), this);
btnClear->setFixedWidth(28);
btnClear->setFlat(true);
btnClear->setToolTip(tr("Ausschlussliste leeren"));
connect(btnClear, &QPushButton::clicked, this, [this]()
{
m_excludeEdit->clear();
});
excludeRow->addWidget(btnClear);
root->addLayout(excludeRow);
// ---- Steuerleiste ----
QHBoxLayout *ctrlRow = new QHBoxLayout();
m_btnAnalyze = new QPushButton(tr("Analyse starten"), this);
ctrlRow->addWidget(m_btnAnalyze);
m_btnExport = new QPushButton(tr("Als TXT exportieren"), this);
m_btnExport->setEnabled(false);
ctrlRow->addWidget(m_btnExport);
ctrlRow->addStretch();
m_statusLabel = new QLabel(this);
m_statusLabel->setStyleSheet("color: palette(mid);");
ctrlRow->addWidget(m_statusLabel);
root->addLayout(ctrlRow);
// ---- Fortschrittsbereich ----
m_progress = new QProgressBar(this);
m_progress->setRange(0, 100);
m_progress->setTextVisible(true);
m_progress->setFormat("%v / %m Dateien");
m_progress->hide();
root->addWidget(m_progress);
m_progressLabel = new QLabel(this);
m_progressLabel->setStyleSheet("color: palette(mid); font-size: 11px;");
m_progressLabel->hide();
root->addWidget(m_progressLabel);
// ---- Ergebnistabelle ----
m_results = new QTreeWidget(this);
m_results->setColumnCount(4);
m_results->setHeaderLabels({
tr("Funktion"),
tr("Klasse"),
tr("Datei"),
tr("Zeile")
});
m_results->setRootIsDecorated(false);
m_results->setAlternatingRowColors(true);
m_results->setSortingEnabled(true);
m_results->sortByColumn(0, Qt::AscendingOrder);
m_results->setSelectionMode(QAbstractItemView::SingleSelection);
m_results->header()->setSectionResizeMode(0, QHeaderView::Interactive);
m_results->header()->setSectionResizeMode(1, QHeaderView::Interactive);
m_results->header()->setSectionResizeMode(2, QHeaderView::Stretch);
m_results->header()->setSectionResizeMode(3, QHeaderView::Fixed);
m_results->header()->resizeSection(0, 200);
m_results->header()->resizeSection(1, 120);
m_results->header()->resizeSection(3, 60);
QFont mono("Monospace");
mono.setStyleHint(QFont::Monospace);
m_results->setFont(mono);
root->addWidget(m_results, 1);
// ---- Verbindungen ----
connect(m_btnAnalyze, &QPushButton::clicked, this, &DeadCodeDialog::onAnalyze);
connect(m_btnExport, &QPushButton::clicked, this, &DeadCodeDialog::onExportClicked);
connect(m_results, &QTreeWidget::itemActivated,
this, &DeadCodeDialog::onItemActivated);
}
// ---------------------------------------------------------------------------
// Öffentliche Schnittstelle
// ---------------------------------------------------------------------------
void DeadCodeDialog::setProjectRoot(const QString &path)
{
// Alte Exclude-Liste für das vorherige Projekt speichern
if (!m_projectRoot.isEmpty())
{
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
const QString oldKey = "deadcode/exclude/" +
QString(m_projectRoot).replace('/', '_').replace('\\', '_');
s.setValue(oldKey, m_excludeEdit->text());
}
m_projectRoot = path;
m_results->clear();
m_statusLabel->setText(QString());
m_btnExport->setEnabled(false);
// Exclude-Liste für das neue Projekt laden
if (!path.isEmpty())
{
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
const QString key = "deadcode/exclude/" +
QString(path).replace('/', '_').replace('\\', '_');
// Projektspezifisch vorhanden? Sonst globalen Fallback nehmen
const QString saved = s.value(key,
s.value("deadcode/excludeDirs", "vendor lib node_modules").toString()
).toString();
m_excludeEdit->setText(saved);
}
}
// ---------------------------------------------------------------------------
// Analyse starten
// ---------------------------------------------------------------------------
void DeadCodeDialog::onAnalyze()
{
if (m_projectRoot.isEmpty())
{
QMessageBox::information(this, tr("Analyse"),
tr("Bitte zuerst ein Projekt öffnen."));
return;
}
if (m_watcher->isRunning())
{
m_watcher->cancel();
m_watcher->waitForFinished();
}
// Exclude-Liste projektspezifisch speichern
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
const QString key = "deadcode/exclude/" +
QString(m_projectRoot).replace('/', '_').replace('\\', '_');
s.setValue(key, m_excludeEdit->text());
s.setValue("deadcode/excludeDirs", m_excludeEdit->text()); // globaler Fallback
const QStringList excludeDirs = m_excludeEdit->text()
.simplified()
.split(' ', Qt::SkipEmptyParts);
m_results->clear();
m_btnAnalyze->setEnabled(false);
m_btnExport->setEnabled(false);
m_progress->setValue(0);
m_progress->setMaximum(0); // Unbestimmter Modus bis wir die Dateizahl kennen
m_progress->show();
m_progressLabel->setText(tr("Dateien werden gezählt…"));
m_progressLabel->show();
m_statusLabel->setText(tr("Analysiere…"));
const QString root = m_projectRoot;
DeadCodeAnalyzer *analyzer = m_analyzer;
QFuture<QList<DeadCodeAnalyzer::DeadFunction>> future =
QtConcurrent::run([analyzer, root, excludeDirs]()
{
return analyzer->analyze(root, {"*.php"}, excludeDirs);
});
m_watcher->setFuture(future);
}
// ---------------------------------------------------------------------------
// Fortschritt aktualisieren (QueuedConnection — Thread-sicher)
// ---------------------------------------------------------------------------
void DeadCodeDialog::onProgressUpdate(int filesScanned, int filesTotal,
const QString &currentFile)
{
if (m_progress->maximum() != filesTotal)
{
m_progress->setMaximum(filesTotal);
}
m_progress->setValue(filesScanned);
m_progressLabel->setText(tr("(%1 / %2) %3")
.arg(filesScanned)
.arg(filesTotal)
.arg(currentFile));
}
// ---------------------------------------------------------------------------
// Analyse abgeschlossen
// ---------------------------------------------------------------------------
void DeadCodeDialog::onAnalysisFinished()
{
m_progress->hide();
m_progressLabel->hide();
m_btnAnalyze->setEnabled(true);
if (m_watcher->isCanceled())
{
return;
}
const QList<DeadCodeAnalyzer::DeadFunction> dead = m_watcher->result();
m_results->setSortingEnabled(false);
for (const DeadCodeAnalyzer::DeadFunction &d : dead)
{
QTreeWidgetItem *item = new QTreeWidgetItem(m_results);
item->setText(0, d.info.signature);
item->setText(1, d.info.className.isEmpty() ? tr("(global)") : d.info.className);
item->setText(2, QFileInfo(d.info.filePath).fileName());
item->setText(3, QString::number(d.info.line));
item->setToolTip(0, d.reason);
item->setToolTip(2, d.info.filePath);
item->setData(0, Qt::UserRole, d.info.filePath);
item->setData(0, Qt::UserRole + 1, d.info.line);
if (d.info.className.isEmpty())
{
item->setForeground(1, QColor("#888888"));
}
}
m_results->setSortingEnabled(true);
m_results->sortByColumn(0, Qt::AscendingOrder);
if (dead.isEmpty())
{
m_statusLabel->setText(tr("✓ Keine ungenutzten Funktionen gefunden."));
m_statusLabel->setStyleSheet("color: #44bb44;");
}
else
{
m_statusLabel->setText(tr("%1 Kandidaten gefunden").arg(dead.size()));
m_statusLabel->setStyleSheet("color: palette(mid);");
m_btnExport->setEnabled(true);
}
}
// ---------------------------------------------------------------------------
// Klick auf Treffer
// ---------------------------------------------------------------------------
void DeadCodeDialog::onItemActivated(QTreeWidgetItem *item, int /*column*/)
{
const QString path = item->data(0, Qt::UserRole).toString();
const int line = item->data(0, Qt::UserRole + 1).toInt();
if (!path.isEmpty() && line > 0)
{
emit fileLineRequested(path, line);
}
}
// ---------------------------------------------------------------------------
// Export
// ---------------------------------------------------------------------------
void DeadCodeDialog::onExportClicked()
{
const QString path = QFileDialog::getSaveFileName(
this,
tr("Ergebnis exportieren"),
QString("barecode_deadcode_%1.txt")
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmm")),
tr("Textdateien (*.txt)")
);
if (path.isEmpty()) { return; }
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::warning(this, tr("Export"),
tr("Datei konnte nicht geschrieben werden:\n%1").arg(path));
return;
}
QTextStream out(&file);
out.setEncoding(QStringConverter::Utf8);
out << "BareCode Analyse ungenutzter Funktionen\n";
out << "Projekt: " << m_projectRoot << "\n";
out << "Ausgeschlossen: " << m_excludeEdit->text() << "\n";
out << "Datum: " << QDateTime::currentDateTime().toString("dd.MM.yyyy HH:mm") << "\n";
out << QString("").repeated(70) << "\n\n";
for (int i = 0; i < m_results->topLevelItemCount(); ++i)
{
QTreeWidgetItem *item = m_results->topLevelItem(i);
out << item->text(0) << "\n";
out << " Klasse: " << item->text(1) << "\n";
out << " Datei: " << item->toolTip(2) << "\n";
out << " Zeile: " << item->text(3) << "\n\n";
}
out << QString("").repeated(70) << "\n";
out << m_results->topLevelItemCount() << " Kandidaten\n";
out << "Hinweis: Dynamische Aufrufe werden nicht erkannt.\n";
QMessageBox::information(this, tr("Export"),
tr("Exportiert nach:\n%1").arg(path));
}
// ---------------------------------------------------------------------------
// Fenstergeometrie speichern
// ---------------------------------------------------------------------------
void DeadCodeDialog::closeEvent(QCloseEvent *event)
{
if (m_watcher->isRunning())
{
m_watcher->cancel();
}
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
s.setValue("deadcode/geometry", saveGeometry());
event->accept();
}

View File

@@ -0,0 +1,62 @@
#pragma once
#include <QDialog>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QLabel>
#include <QPushButton>
#include <QProgressBar>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFutureWatcher>
#include <QString>
#include "DeadCodeAnalyzer.h"
class DeadCodeDialog : public QDialog
{
Q_OBJECT
public:
explicit DeadCodeDialog(QWidget *parent = nullptr);
void setProjectRoot(const QString &path);
signals:
void fileLineRequested(const QString &filePath, int line);
private slots:
void onAnalyze();
void onAnalysisFinished();
void onProgressUpdate(int filesScanned, int filesTotal,
const QString &currentFile);
void onItemActivated(QTreeWidgetItem *item, int column);
void onExportClicked();
protected:
void closeEvent(QCloseEvent *event) override;
private:
void setupUi();
QString m_projectRoot;
// Einstellungen
QLineEdit *m_excludeEdit = nullptr;
// Steuerung
QPushButton *m_btnAnalyze = nullptr;
QPushButton *m_btnExport = nullptr;
// Fortschritt
QProgressBar *m_progress = nullptr;
QLabel *m_progressLabel = nullptr;
// Status + Ergebnisse
QLabel *m_statusLabel = nullptr;
QTreeWidget *m_results = nullptr;
DeadCodeAnalyzer *m_analyzer = nullptr;
QFutureWatcher<QList<DeadCodeAnalyzer::DeadFunction>> *m_watcher = nullptr;
};

View File

@@ -3,6 +3,9 @@
#include "CodeEditor.h"
#include "SearchPanel.h"
#include "FileSearchPanel.h"
#include "FunctionListDialog.h"
#include "DeadCodeDialog.h"
#include "FunctionIndex.h"
#include <QFileInfo>
#include <QFile>
@@ -32,6 +35,9 @@ void EditorPanel::setupUi()
m_searchPanel = new SearchPanel(this);
m_fileSearch = new FileSearchPanel(this);
m_funcDialog = new FunctionListDialog(window());
m_deadCode = new DeadCodeDialog(window());
m_funcIndex = new FunctionIndex(this);
m_layout->addWidget(m_tabWidget, 1);
m_layout->addWidget(m_searchPanel, 0);
@@ -45,6 +51,12 @@ void EditorPanel::setupUi()
connect(m_fileSearch, &FileSearchPanel::fileLineRequested,
this, &EditorPanel::goToLine);
connect(m_funcDialog, &FunctionListDialog::fileLineRequested,
this, &EditorPanel::goToLine);
connect(m_deadCode, &DeadCodeDialog::fileLineRequested,
this, &EditorPanel::goToLine);
}
// ---------------------------------------------------------------------------
@@ -79,6 +91,13 @@ void EditorPanel::openFile(const QString &filePath)
m_tabWidget->setTabToolTip(index, filePath);
m_openTabs.insert(filePath, tab);
// FunctionIndex dem Editor mitgeben für Doppelklick-Navigation
tab->editor()->setFunctionIndex(m_funcIndex);
// Doppelklick auf Funktion → zur Definition springen
connect(tab->editor(), &CodeEditor::navigateToRequested,
this, &EditorPanel::goToLine);
// Änderungsindikator im Tab-Titel (● = ungespeichert)
connect(tab->editor()->document(), &QTextDocument::modificationChanged,
this, [this, tab](bool modified)
@@ -102,6 +121,13 @@ void EditorPanel::openFile(const QString &filePath)
m_tabWidget->setTabToolTip(idx, savedPath);
}
emit currentFileSaved(savedPath);
// Index und Funktionsliste aktualisieren
m_funcIndex->refresh();
if (m_funcDialog->isVisible())
{
m_funcDialog->refresh();
}
});
}
@@ -145,9 +171,26 @@ void EditorPanel::showFileSearchPanel()
m_fileSearch->activate();
}
void EditorPanel::showFunctionList()
{
m_funcDialog->show();
m_funcDialog->raise();
m_funcDialog->activateWindow();
}
void EditorPanel::showDeadCode()
{
m_deadCode->show();
m_deadCode->raise();
m_deadCode->activateWindow();
}
void EditorPanel::setSearchRoot(const QString &path)
{
m_fileSearch->setSearchRoot(path);
m_funcDialog->setProjectRoot(path);
m_deadCode->setProjectRoot(path);
m_funcIndex->setProjectRoot(path);
}
void EditorPanel::goToLine(const QString &filePath, int line)

View File

@@ -10,6 +10,9 @@ class EditorTab;
class Settings;
class SearchPanel;
class FileSearchPanel;
class FunctionListDialog;
class DeadCodeDialog;
class FunctionIndex;
// ---------------------------------------------------------------------------
// EditorPanel Rechtes Panel: Tab-Leiste + Editoren + Such/Ersetzen-Panel.
@@ -29,6 +32,8 @@ public slots:
void setSearchRoot(const QString &path);
void showSearchPanel();
void showFileSearchPanel();
void showFunctionList();
void showDeadCode();
void goToLine(const QString &filePath, int line);
// Session
@@ -55,6 +60,9 @@ private:
QTabWidget *m_tabWidget = nullptr;
SearchPanel *m_searchPanel = nullptr;
FileSearchPanel *m_fileSearch = nullptr;
FunctionListDialog *m_funcDialog = nullptr;
DeadCodeDialog *m_deadCode = nullptr;
FunctionIndex *m_funcIndex = nullptr;
QHash<QString, EditorTab *> m_openTabs;
};

View File

@@ -0,0 +1,83 @@
#include "FunctionIndex.h"
#include <QtConcurrent/QtConcurrent>
FunctionIndex::FunctionIndex(QObject *parent)
: QObject(parent)
{
m_scanner = new FunctionScanner(this);
m_watcher = new QFutureWatcher<QList<FunctionScanner::FunctionInfo>>(this);
connect(m_watcher, &QFutureWatcher<QList<FunctionScanner::FunctionInfo>>::finished,
this, &FunctionIndex::onScanFinished);
}
void FunctionIndex::setProjectRoot(const QString &path)
{
m_projectRoot = path;
m_ready = false;
m_index.clear();
if (!path.isEmpty())
{
refresh();
}
}
void FunctionIndex::refresh()
{
if (m_projectRoot.isEmpty())
{
return;
}
if (m_watcher->isRunning())
{
m_watcher->cancel();
m_watcher->waitForFinished();
}
FunctionScanner *scanner = m_scanner;
const QString root = m_projectRoot;
m_watcher->setFuture(
QtConcurrent::run([scanner, root]()
{
return scanner->scanDirectory(root);
})
);
}
void FunctionIndex::onScanFinished()
{
if (m_watcher->isCanceled())
{
return;
}
m_index.clear();
const QList<FunctionScanner::FunctionInfo> all = m_watcher->result();
for (const FunctionScanner::FunctionInfo &func : all)
{
const QString key = func.name.toLower();
// Ersten Eintrag bevorzugen (globale Funktionen vor Methoden)
if (!m_index.contains(key) || func.className.isEmpty())
{
m_index.insert(key, func);
}
}
m_ready = true;
emit indexReady();
}
FunctionScanner::FunctionInfo FunctionIndex::lookup(const QString &name) const
{
return m_index.value(name.toLower(), FunctionScanner::FunctionInfo{});
}
bool FunctionIndex::isReady() const
{
return m_ready;
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include <QObject>
#include <QHash>
#include <QString>
#include <QStringList>
#include <QFutureWatcher>
#include "FunctionScanner.h"
// ---------------------------------------------------------------------------
// FunctionIndex Singleton-artiger Index aller Projektfunktionen.
// Wird von CodeEditor (Doppelklick-Navigation) und SignatureHelper genutzt.
// Aktualisiert sich asynchron wenn setProjectRoot() aufgerufen wird.
// ---------------------------------------------------------------------------
class FunctionIndex : public QObject
{
Q_OBJECT
public:
explicit FunctionIndex(QObject *parent = nullptr);
void setProjectRoot(const QString &path);
void refresh();
// Sucht eine Funktion nach Name (Groß-/Kleinschreibung ignoriert)
// Gibt ungültige FunctionInfo zurück wenn nicht gefunden (filePath ist leer)
FunctionScanner::FunctionInfo lookup(const QString &name) const;
bool isReady() const;
signals:
void indexReady();
private slots:
void onScanFinished();
private:
QString m_projectRoot;
FunctionScanner *m_scanner = nullptr;
QFutureWatcher<QList<FunctionScanner::FunctionInfo>> *m_watcher = nullptr;
// name.toLower() → FunctionInfo
QHash<QString, FunctionScanner::FunctionInfo> m_index;
bool m_ready = false;
};

View File

@@ -0,0 +1,52 @@
#include "FunctionListDialog.h"
#include "FunctionListPanel.h"
#include <QVBoxLayout>
#include <QSettings>
#include <QCloseEvent>
FunctionListDialog::FunctionListDialog(QWidget *parent)
: QDialog(parent, Qt::Window)
{
setWindowTitle(tr("Projektfunktionen BareCode"));
setMinimumSize(500, 400);
// Fenstergröße und -position wiederherstellen
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
if (s.contains("funclist/geometry"))
{
restoreGeometry(s.value("funclist/geometry").toByteArray());
}
else
{
resize(600, 700);
}
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
m_panel = new FunctionListPanel(this);
m_panel->show(); // Immer sichtbar — Panel ist jetzt der gesamte Dialog-Inhalt
layout->addWidget(m_panel);
connect(m_panel, &FunctionListPanel::fileLineRequested,
this, &FunctionListDialog::fileLineRequested);
}
void FunctionListDialog::setProjectRoot(const QString &path)
{
m_panel->setProjectRoot(path);
}
void FunctionListDialog::refresh()
{
m_panel->refresh();
}
void FunctionListDialog::closeEvent(QCloseEvent *event)
{
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
s.setValue("funclist/geometry", saveGeometry());
event->accept();
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <QDialog>
#include <QString>
class FunctionListPanel;
// ---------------------------------------------------------------------------
// FunctionListDialog Eigenständiges Fenster für die Projektfunktionen.
// Bleibt offen während man im Editor arbeitet (non-modal).
// ---------------------------------------------------------------------------
class FunctionListDialog : public QDialog
{
Q_OBJECT
public:
explicit FunctionListDialog(QWidget *parent = nullptr);
void setProjectRoot(const QString &path);
void refresh();
protected:
void closeEvent(QCloseEvent *event) override;
signals:
void fileLineRequested(const QString &filePath, int line);
private:
FunctionListPanel *m_panel = nullptr;
};

View File

@@ -0,0 +1,332 @@
#include "FunctionListPanel.h"
#include <QtConcurrent/QtConcurrent>
#include <QFileInfo>
#include <QFont>
#include <QProgressBar>
FunctionListPanel::FunctionListPanel(QWidget *parent)
: QWidget(parent)
{
m_scanner = new FunctionScanner(this);
m_watcher = new QFutureWatcher<QList<FunctionScanner::FunctionInfo>>(this);
connect(m_watcher, &QFutureWatcher<QList<FunctionScanner::FunctionInfo>>::finished,
this, &FunctionListPanel::onScanFinished);
setupUi();
}
// ---------------------------------------------------------------------------
// UI
// ---------------------------------------------------------------------------
void FunctionListPanel::setupUi()
{
QVBoxLayout *root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->setSpacing(0);
// ---- Kopfzeile ----
QWidget *header = new QWidget(this);
header->setStyleSheet("background: palette(mid);");
QHBoxLayout *headerLayout = new QHBoxLayout(header);
headerLayout->setContentsMargins(6, 4, 6, 4);
headerLayout->setSpacing(4);
QLabel *title = new QLabel(tr("Projektfunktionen"), header);
QFont boldFont = title->font();
boldFont.setBold(true);
title->setFont(boldFont);
headerLayout->addWidget(title);
headerLayout->addStretch();
m_btnRefresh = new QPushButton(tr(""), header);
m_btnRefresh->setFixedSize(24, 24);
m_btnRefresh->setFlat(true);
m_btnRefresh->setToolTip(tr("Neu scannen"));
headerLayout->addWidget(m_btnRefresh);
root->addWidget(header);
// ---- Filter + Gruppierung ----
QHBoxLayout *toolRow = new QHBoxLayout();
toolRow->setContentsMargins(6, 4, 6, 4);
toolRow->setSpacing(4);
m_filterEdit = new QLineEdit(this);
m_filterEdit->setPlaceholderText(tr("Funktion suchen…"));
m_filterEdit->setClearButtonEnabled(true);
toolRow->addWidget(m_filterEdit, 1);
m_groupCombo = new QComboBox(this);
m_groupCombo->addItem(tr("Nach Datei"), "file");
m_groupCombo->addItem(tr("Nach Klasse"), "class");
m_groupCombo->addItem(tr("Alphabetisch"), "alpha");
m_groupCombo->setFixedWidth(120);
toolRow->addWidget(m_groupCombo);
root->addLayout(toolRow);
// ---- Status ----
m_statusLabel = new QLabel(this);
m_statusLabel->setContentsMargins(6, 0, 6, 2);
m_statusLabel->setStyleSheet("color: palette(mid); font-size: 11px;");
root->addWidget(m_statusLabel);
// ---- Ergebnisbaum ----
m_tree = new QTreeWidget(this);
m_tree->setHeaderHidden(true);
m_tree->setRootIsDecorated(true);
m_tree->setIndentation(14);
m_tree->setAlternatingRowColors(false);
m_tree->setAnimated(true);
// Monospace-Font für Parameter
QFont monoFont("Monospace", m_tree->font().pointSize());
monoFont.setStyleHint(QFont::Monospace);
m_tree->setFont(monoFont);
root->addWidget(m_tree, 1);
// ---- Verbindungen ----
connect(m_btnRefresh, &QPushButton::clicked,
this, &FunctionListPanel::refresh);
connect(m_filterEdit, &QLineEdit::textChanged,
this, &FunctionListPanel::onFilterChanged);
connect(m_groupCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &FunctionListPanel::onGroupingChanged);
connect(m_tree, &QTreeWidget::itemActivated,
this, &FunctionListPanel::onItemActivated);
}
// ---------------------------------------------------------------------------
// Öffentliche Schnittstelle
// ---------------------------------------------------------------------------
void FunctionListPanel::setProjectRoot(const QString &path)
{
m_projectRoot = path;
if (!path.isEmpty())
{
refresh();
}
else
{
m_tree->clear();
m_statusLabel->setText(QString());
m_lastResult.clear();
}
}
void FunctionListPanel::activate()
{
m_filterEdit->setFocus();
m_filterEdit->selectAll();
if (m_lastResult.isEmpty() && !m_projectRoot.isEmpty())
{
refresh();
}
}
// ---------------------------------------------------------------------------
// Scan starten
// ---------------------------------------------------------------------------
void FunctionListPanel::refresh()
{
if (m_projectRoot.isEmpty())
{
m_statusLabel->setText(tr("Kein Projekt geöffnet."));
return;
}
if (m_watcher->isRunning())
{
m_watcher->cancel();
m_watcher->waitForFinished();
}
m_statusLabel->setText(tr("Scanne…"));
m_btnRefresh->setEnabled(false);
const QString root = m_projectRoot;
FunctionScanner *scanner = m_scanner;
QFuture<QList<FunctionScanner::FunctionInfo>> future =
QtConcurrent::run([scanner, root]()
{
return scanner->scanDirectory(root);
});
m_watcher->setFuture(future);
}
// ---------------------------------------------------------------------------
// Scan abgeschlossen
// ---------------------------------------------------------------------------
void FunctionListPanel::onScanFinished()
{
m_btnRefresh->setEnabled(true);
if (m_watcher->isCanceled())
{
return;
}
m_lastResult = m_watcher->result();
populateTree(m_lastResult);
}
// ---------------------------------------------------------------------------
// Baum befüllen
// ---------------------------------------------------------------------------
void FunctionListPanel::populateTree(const QList<FunctionScanner::FunctionInfo> &functions)
{
m_tree->clear();
const QString groupBy = m_groupCombo->currentData().toString();
const QString filter = m_filterEdit->text().trimmed().toLower();
// Funktionen filtern
QList<FunctionScanner::FunctionInfo> filtered;
for (const auto &f : functions)
{
if (filter.isEmpty() ||
f.name.toLower().contains(filter) ||
f.parameters.toLower().contains(filter) ||
f.className.toLower().contains(filter))
{
filtered.append(f);
}
}
if (groupBy == "alpha")
{
// Alphabetisch — keine Gruppen
std::sort(filtered.begin(), filtered.end(),
[](const FunctionScanner::FunctionInfo &a,
const FunctionScanner::FunctionInfo &b)
{
return a.name.toLower() < b.name.toLower();
});
for (const auto &f : filtered)
{
QTreeWidgetItem *item = new QTreeWidgetItem(m_tree);
formatFunctionItem(item, f);
}
}
else
{
// Gruppiert nach Datei oder Klasse
QMap<QString, QList<FunctionScanner::FunctionInfo>> groups;
for (const auto &f : filtered)
{
QString key;
if (groupBy == "class")
{
key = f.className.isEmpty() ? tr("(global)") : f.className;
}
else
{
key = QFileInfo(f.filePath).fileName();
}
groups[key].append(f);
}
for (auto it = groups.begin(); it != groups.end(); ++it)
{
QTreeWidgetItem *groupItem = new QTreeWidgetItem(m_tree);
groupItem->setText(0, QString("%1 (%2)")
.arg(it.key())
.arg(it.value().size()));
groupItem->setData(0, Qt::UserRole, QString());
groupItem->setData(0, Qt::UserRole + 1, -1);
QFont boldFont = groupItem->font(0);
boldFont.setBold(true);
groupItem->setFont(0, boldFont);
groupItem->setExpanded(true);
// Funktionen innerhalb der Gruppe alphabetisch
auto &list = it.value();
std::sort(list.begin(), list.end(),
[](const FunctionScanner::FunctionInfo &a,
const FunctionScanner::FunctionInfo &b)
{
return a.name.toLower() < b.name.toLower();
});
for (const auto &f : list)
{
QTreeWidgetItem *item = new QTreeWidgetItem(groupItem);
formatFunctionItem(item, f);
}
}
}
// Statuszeile
const int total = functions.size();
const int shown = filtered.size();
if (filter.isEmpty())
{
m_statusLabel->setText(tr("%1 Funktionen gefunden").arg(total));
}
else
{
m_statusLabel->setText(tr("%1 von %2 Funktionen").arg(shown).arg(total));
}
}
void FunctionListPanel::formatFunctionItem(QTreeWidgetItem *item,
const FunctionScanner::FunctionInfo &f)
{
// Anzeige: name(parameter) — Zeilennummer klein dahinter
const QString display = QString("%1(%2)")
.arg(f.name)
.arg(f.parameters);
item->setText(0, display);
item->setToolTip(0, QString("%1\nZeile %2").arg(f.filePath).arg(f.line));
item->setData(0, Qt::UserRole, f.filePath);
item->setData(0, Qt::UserRole + 1, f.line);
// Zeilennummer in gedimmter Farbe als zweite Spalte simulieren
// (über ToolTip gelöst da wir nur eine Spalte haben)
}
// ---------------------------------------------------------------------------
// Slots
// ---------------------------------------------------------------------------
void FunctionListPanel::onItemActivated(QTreeWidgetItem *item, int /*column*/)
{
const QString path = item->data(0, Qt::UserRole).toString();
const int line = item->data(0, Qt::UserRole + 1).toInt();
if (path.isEmpty() || line < 0)
{
item->setExpanded(!item->isExpanded());
return;
}
emit fileLineRequested(path, line);
}
void FunctionListPanel::onFilterChanged(const QString &text)
{
Q_UNUSED(text)
if (!m_lastResult.isEmpty())
{
populateTree(m_lastResult);
}
}
void FunctionListPanel::onGroupingChanged(int /*index*/)
{
if (!m_lastResult.isEmpty())
{
populateTree(m_lastResult);
}
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <QWidget>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QLineEdit>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QComboBox>
#include <QFutureWatcher>
#include <QString>
#include "FunctionScanner.h"
// ---------------------------------------------------------------------------
// FunctionListPanel Zeigt alle im Projekt definierten Funktionen
// als durchsuchbare, sortierbare Liste.
//
// Gruppierung: nach Datei oder nach Klasse
// Klick: öffnet Datei und springt zur Definition
// Aktualisierung: automatisch nach jedem Speichern
// ---------------------------------------------------------------------------
class FunctionListPanel : public QWidget
{
Q_OBJECT
public:
explicit FunctionListPanel(QWidget *parent = nullptr);
void setProjectRoot(const QString &path);
public slots:
void refresh();
void activate();
signals:
void fileLineRequested(const QString &filePath, int line);
private slots:
void onScanFinished();
void onItemActivated(QTreeWidgetItem *item, int column);
void onFilterChanged(const QString &text);
void onGroupingChanged(int index);
private:
void setupUi();
void populateTree(const QList<FunctionScanner::FunctionInfo> &functions);
void applyFilter(const QString &text);
static void formatFunctionItem(QTreeWidgetItem *item,
const FunctionScanner::FunctionInfo &f);
QString m_projectRoot;
QLineEdit *m_filterEdit = nullptr;
QComboBox *m_groupCombo = nullptr;
QPushButton *m_btnRefresh = nullptr;
QLabel *m_statusLabel = nullptr;
QTreeWidget *m_tree = nullptr;
FunctionScanner *m_scanner = nullptr;
QFutureWatcher<QList<FunctionScanner::FunctionInfo>> *m_watcher = nullptr;
// Letztes Scan-Ergebnis für Filter ohne Neuscan
QList<FunctionScanner::FunctionInfo> m_lastResult;
};

View File

@@ -0,0 +1,158 @@
#include "FunctionScanner.h"
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
FunctionScanner::FunctionScanner(QObject *parent)
: QObject(parent)
{
}
// ---------------------------------------------------------------------------
// Verzeichnis rekursiv scannen
// ---------------------------------------------------------------------------
QList<FunctionScanner::FunctionInfo> FunctionScanner::scanDirectory(
const QString &rootPath,
const QStringList &extensions) const
{
QList<FunctionInfo> results;
QDirIterator it(rootPath,
extensions,
QDir::Files,
QDirIterator::Subdirectories);
while (it.hasNext())
{
const QString path = it.next();
results.append(scanFile(path));
}
return results;
}
// ---------------------------------------------------------------------------
// Einzelne Datei scannen
// ---------------------------------------------------------------------------
QList<FunctionScanner::FunctionInfo> FunctionScanner::scanFile(const QString &filePath) const
{
QList<FunctionInfo> results;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
return results;
}
QTextStream stream(&file);
stream.setEncoding(QStringConverter::Utf8);
QStringList lines;
while (!stream.atEnd())
{
lines.append(stream.readLine());
}
// Regex für Funktionsdefinitionen:
// optional: public/protected/private/static/abstract/final
// gefolgt von: function name(
static const QRegularExpression funcRegex(
R"((?:(?:public|protected|private|static|abstract|final)\s+)*)"
R"(function\s+(&?\s*[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\s*\()",
QRegularExpression::CaseInsensitiveOption
);
for (int i = 0; i < lines.size(); ++i)
{
const QString &line = lines[i];
// Zeilen in Kommentaren überspringen
const QString trimmed = line.trimmed();
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("#"))
{
continue;
}
QRegularExpressionMatch match = funcRegex.match(line);
if (!match.hasMatch())
{
continue;
}
FunctionInfo info;
info.name = match.captured(1).trimmed();
info.filePath = filePath;
info.line = i + 1;
info.className = detectClassContext(lines, i);
info.parameters = extractParameters(line, static_cast<int>(match.capturedEnd()) - 1);
info.signature = info.name + "(" + info.parameters + ")";
// Konstruktoren und magische Methoden kennzeichnen
// aber trotzdem aufnehmen — sie sind nützlich in der Liste
results.append(info);
}
return results;
}
// ---------------------------------------------------------------------------
// Parameter aus der Funktionssignatur extrahieren
// Behandelt auch mehrzeilige Signaturen mit öffnender Klammer am Ende
// ---------------------------------------------------------------------------
QString FunctionScanner::extractParameters(const QString &line, int parenPos)
{
// Inhalt zwischen ( und ) extrahieren
// Einfache Version: nur die erste Zeile — reicht für 99% aller Fälle
const int openParen = parenPos;
int depth = 0;
int closePos = -1;
for (int i = openParen; i < line.length(); ++i)
{
if (line[i] == '(') { ++depth; }
else if (line[i] == ')')
{
--depth;
if (depth == 0)
{
closePos = i;
break;
}
}
}
if (closePos == -1)
{
// Klammer geht über Zeilenende — gekürzt anzeigen
return line.mid(openParen + 1).trimmed() + "";
}
return line.mid(openParen + 1, closePos - openParen - 1).trimmed();
}
// ---------------------------------------------------------------------------
// Klassenkontext erkennen — rückwärts durch den Code suchen
// ---------------------------------------------------------------------------
QString FunctionScanner::detectClassContext(const QStringList &lines, int functionLineIndex)
{
static const QRegularExpression classRegex(
R"((?:class|interface|trait|enum)\s+([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*))",
QRegularExpression::CaseInsensitiveOption
);
// Rückwärts suchen — letztes class/interface/trait vor dieser Zeile
for (int i = functionLineIndex - 1; i >= 0; --i)
{
QRegularExpressionMatch match = classRegex.match(lines[i]);
if (match.hasMatch())
{
return match.captured(1);
}
}
return QString(); // Globale Funktion
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include <QObject>
#include <QString>
#include <QStringList>
#include <QList>
// ---------------------------------------------------------------------------
// FunctionScanner Scannt PHP-Dateien nach Funktionsdefinitionen.
//
// Liefert für jede gefundene Funktion:
// - Name
// - Parameter (vollständige Signatur)
// - Dateipfad
// - Zeilennummer
//
// Wird von FunctionListPanel (Anzeige) und DeadCodeAnalyzer (Stufe 1)
// gemeinsam genutzt.
// ---------------------------------------------------------------------------
class FunctionScanner : public QObject
{
Q_OBJECT
public:
struct FunctionInfo
{
QString name; // Funktionsname
QString parameters; // Parameter wie definiert, z.B. "$id, $name = null"
QString signature; // name(parameters) — fertig formatiert
QString filePath; // Absoluter Pfad zur Datei
int line = 0; // Zeilennummer der Definition
QString className; // Klassen- oder Namespace-Kontext, leer = global
};
explicit FunctionScanner(QObject *parent = nullptr);
// Synchroner Scan — für direkten Aufruf aus Threads
QList<FunctionInfo> scanDirectory(const QString &rootPath,
const QStringList &extensions = {"*.php"}) const;
// Scannt eine einzelne Datei
QList<FunctionInfo> scanFile(const QString &filePath) const;
private:
static QString extractParameters(const QString &line, int parenPos);
static QString detectClassContext(const QStringList &lines, int functionLineIndex);
};

View File

@@ -0,0 +1,256 @@
#include "VariableCompleter.h"
#include "CodeEditor.h"
#include <QTextCursor>
#include <QTextBlock>
#include <QKeyEvent>
#include <QListWidgetItem>
#include <QAbstractItemView>
#include <QScrollBar>
#include <QApplication>
#include <QScreen>
VariableCompleter::VariableCompleter(CodeEditor *editor)
: QObject(editor)
, m_editor(editor)
{
// Popup als reines Anzeige-Fenster — dieselbe Fensterart wie
// SignatureTooltip, die nachweislich nie den Fokus an sich reißt.
// Wichtig: Ein echtes Elternfenster (editor->window()) angeben, sonst
// behandelt der Fenstermanager das Popup als eigenständiges Fenster.
// Das führte dazu, dass bei mehreren offenen Dateien/Tabs verwaiste
// Popups entstehen konnten, die den Fokus übernehmen und die
// Texteingabe blockieren.
m_popup = new QListWidget(editor->window());
m_popup->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
m_popup->setAttribute(Qt::WA_ShowWithoutActivating);
m_popup->setFocusPolicy(Qt::NoFocus);
m_popup->setMaximumHeight(200);
m_popup->setMinimumWidth(180);
m_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_popup->setStyleSheet(
"QListWidget {"
" border: 1px solid palette(mid);"
" background: palette(toolTipBase);"
" color: palette(toolTipText);"
" font-family: Monospace;"
"}"
"QListWidget::item:selected {"
" background: palette(highlight);"
" color: palette(highlightedText);"
"}"
);
connect(m_popup, &QListWidget::itemActivated,
this, &VariableCompleter::onItemActivated);
}
// ---------------------------------------------------------------------------
// Nach jedem Tastendruck aufrufen
// ---------------------------------------------------------------------------
void VariableCompleter::handleKeyPress(QKeyEvent *event)
{
// Popup-Navigation
if (m_popup->isVisible())
{
if (event->key() == Qt::Key_Escape)
{
hidePopup();
return;
}
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
{
if (m_popup->currentItem())
{
onItemActivated(m_popup->currentItem());
}
hidePopup();
return;
}
if (event->key() == Qt::Key_Down)
{
const int next = qMin(m_popup->currentRow() + 1,
m_popup->count() - 1);
m_popup->setCurrentRow(next);
return;
}
if (event->key() == Qt::Key_Up)
{
const int prev = qMax(m_popup->currentRow() - 1, 0);
m_popup->setCurrentRow(prev);
return;
}
}
// Nach dem normalen Tastendruck Popup aktualisieren
// (der Event wurde bereits an QPlainTextEdit weitergegeben)
updatePopup();
}
// ---------------------------------------------------------------------------
// Popup aktualisieren
// ---------------------------------------------------------------------------
void VariableCompleter::updatePopup()
{
const QString prefix = currentPrefix();
// Nur bei $ und mindestens einem weiteren Zeichen anzeigen
if (prefix.length() < 2 || !prefix.startsWith('$'))
{
hidePopup();
return;
}
const QStringList allVars = collectVariables();
const QString filter = prefix.toLower();
QStringList matches;
for (const QString &var : allVars)
{
if (var.toLower().startsWith(filter) && var != prefix)
{
matches.append(var);
}
}
if (matches.isEmpty())
{
hidePopup();
return;
}
// Popup befüllen
m_popup->clear();
for (const QString &var : matches)
{
m_popup->addItem(var);
}
m_popup->setCurrentRow(0);
// Größe anpassen
const int itemHeight = m_popup->sizeHintForRow(0) + 2;
const int height = qMin(matches.size() * itemHeight + 4, 200);
m_popup->setFixedHeight(height);
// Position unter dem Cursor
const QRect cursorRect = m_editor->cursorRect();
QPoint pos = m_editor->viewport()->mapToGlobal(
QPoint(cursorRect.left(), cursorRect.bottom() + 2)
);
// Nicht aus dem Bildschirm herausragen
const QRect screen = QApplication::primaryScreen()->availableGeometry();
if (pos.x() + m_popup->width() > screen.right())
{
pos.setX(screen.right() - m_popup->width());
}
if (pos.y() + height > screen.bottom())
{
pos.setY(cursorRect.top() - height - 2);
}
m_popup->move(pos);
m_popup->show();
m_popup->raise();
}
void VariableCompleter::hidePopup()
{
m_popup->hide();
m_popup->clear();
}
// ---------------------------------------------------------------------------
// Editor hat den Fokus verloren (z. B. Tab-Wechsel) — Popup schließen
// ---------------------------------------------------------------------------
void VariableCompleter::notifyFocusLost()
{
hidePopup();
}
// ---------------------------------------------------------------------------
// Variablen im aktuellen Dokument sammeln
// ---------------------------------------------------------------------------
QStringList VariableCompleter::collectVariables() const
{
static const QRegularExpression varRegex(R"(\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)");
QSet<QString> seen;
QStringList result;
const QString text = m_editor->toPlainText();
QRegularExpressionMatchIterator it = varRegex.globalMatch(text);
while (it.hasNext())
{
const QString var = it.next().captured(0);
if (!seen.contains(var))
{
seen.insert(var);
result.append(var);
}
}
result.sort(Qt::CaseInsensitive);
return result;
}
// ---------------------------------------------------------------------------
// Text links vom Cursor — das angefangene $variable
// ---------------------------------------------------------------------------
QString VariableCompleter::currentPrefix() const
{
const QTextCursor cursor = m_editor->textCursor();
const QString line = cursor.block().text();
const int col = cursor.columnNumber();
if (col == 0)
{
return QString();
}
// Rückwärts gehen bis zum $ oder einem Nicht-Bezeichner-Zeichen
int start = col - 1;
while (start > 0)
{
const QChar c = line[start - 1];
if (!c.isLetterOrNumber() && c != '_' && c != '$')
{
break;
}
--start;
}
const QString token = line.mid(start, col - start);
return token.startsWith('$') ? token : QString();
}
// ---------------------------------------------------------------------------
// Klick oder Enter — Variable einfügen
// ---------------------------------------------------------------------------
void VariableCompleter::onItemActivated(QListWidgetItem *item)
{
if (!item)
{
return;
}
const QString selected = item->text();
const QString prefix = currentPrefix();
if (prefix.isEmpty())
{
hidePopup();
return;
}
// Prefix durch den vollständigen Variablennamen ersetzen
QTextCursor cursor = m_editor->textCursor();
cursor.movePosition(QTextCursor::Left,
QTextCursor::KeepAnchor,
prefix.length());
cursor.insertText(selected);
m_editor->setTextCursor(cursor);
hidePopup();
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include <QObject>
#include <QListWidget>
#include <QStringList>
#include <QRegularExpression>
class CodeEditor;
// ---------------------------------------------------------------------------
// VariableCompleter Zeigt ein Popup mit passenden Variablennamen
// wenn der Nutzer $ tippt und weiter eingibt.
//
// Kein Autocomplete — nur Anzeige. Klick oder Enter übernimmt.
// Escape oder Weiterschreiben ohne Treffer schließt das Popup.
// ---------------------------------------------------------------------------
class VariableCompleter : public QObject
{
Q_OBJECT
public:
explicit VariableCompleter(CodeEditor *editor);
// Muss nach jedem Tastendruck aufgerufen werden
void handleKeyPress(QKeyEvent *event);
// Muss aufgerufen werden, wenn der Editor den Fokus verliert
// (z. B. beim Wechsel des Tabs) — schließt ein evtl. offenes Popup,
// damit es nicht als "Geisterfenster" stehen bleibt.
void notifyFocusLost();
private slots:
void onItemActivated(QListWidgetItem *item);
private:
void updatePopup();
void hidePopup();
QStringList collectVariables() const;
QString currentPrefix() const; // "$me" etc. links vom Cursor
CodeEditor *m_editor = nullptr;
QListWidget *m_popup = nullptr;
};

View File

@@ -11,6 +11,7 @@ SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent)
void SyntaxHighlighter::highlightBlock(const QString &text)
{
// Einzel-Zeilen-Regeln anwenden
for (const HighlightRule &rule : m_rules)
{
QRegularExpressionMatchIterator it = rule.pattern.globalMatch(text);
@@ -27,21 +28,65 @@ void SyntaxHighlighter::highlightBlock(const QString &text)
if (!m_hasMultiLineComment)
{
setCurrentBlockState(0);
return;
}
// Mehrzeilige Kommentare
// Zustand 0 = normal, 1 = mitten in /* ... */
setCurrentBlockState(0);
int startIndex = 0;
if (previousBlockState() != 1)
// Leere Zeile mitten im Kommentar — Zustand weitertragen
if (text.isEmpty())
{
if (previousBlockState() == 1)
{
setCurrentBlockState(1);
}
return;
}
int startIndex = 0;
if (previousBlockState() == 1)
{
// Vorherige Zeile war mitten im Kommentar — ab Zeilenanfang nach Ende suchen
QRegularExpressionMatch endMatch = m_commentEndExpression.match(text, 0);
if (endMatch.hasMatch())
{
// Ende des Kommentars gefunden
const int commentLength = static_cast<int>(endMatch.capturedStart())
+ static_cast<int>(endMatch.capturedLength());
setFormat(0, commentLength, m_multiLineCommentFormat);
// Nach weiteren Kommentaren in der gleichen Zeile suchen
QRegularExpressionMatch nextStart =
m_commentStartExpression.match(text, commentLength);
startIndex = nextStart.hasMatch()
? static_cast<int>(nextStart.capturedStart())
: -1;
}
else
{
// Noch kein Ende — gesamte Zeile ist Kommentar
setFormat(0, text.length(), m_multiLineCommentFormat);
setCurrentBlockState(1);
return;
}
}
else
{
// Neuen Kommentaranfang suchen
QRegularExpressionMatch m = m_commentStartExpression.match(text);
startIndex = m.hasMatch() ? static_cast<int>(m.capturedStart()) : -1;
}
while (startIndex >= 0)
{
QRegularExpressionMatch endMatch = m_commentEndExpression.match(text, startIndex);
QRegularExpressionMatch endMatch =
m_commentEndExpression.match(text, startIndex);
int commentLength = 0;
if (endMatch.hasMatch())
@@ -52,6 +97,7 @@ void SyntaxHighlighter::highlightBlock(const QString &text)
}
else
{
// Kommentar geht über Zeilenende
setCurrentBlockState(1);
commentLength = text.length() - startIndex;
}
@@ -63,6 +109,7 @@ void SyntaxHighlighter::highlightBlock(const QString &text)
break;
}
// Nach weiteren Kommentaren in dieser Zeile suchen
QRegularExpressionMatch nextStart =
m_commentStartExpression.match(text, startIndex + commentLength);
startIndex = nextStart.hasMatch()
@@ -374,14 +421,22 @@ void PhpHighlighter::highlightPhpRange(const QString &text, int start, int lengt
}
}
// /* */ Block-Kommentare innerhalb des PHP-Bereichs mehrzeilig behandeln.
// Block-Zustand 3 = wir sind mitten in einem /* ... */ PHP-Kommentar.
static const QRegularExpression blockOpen(R"(/\*)");
static const QRegularExpression blockClose(R"(\*/)");
int searchFrom = 0;
// Waren wir bereits in einem Block-Kommentar?
// Leere Zeile mitten im PHP-Blockkommentar — Zustand weitertragen
if (phpText.trimmed().isEmpty())
{
if (previousBlockState() == 3)
{
setFormat(start, length, m_phpCommentFormat);
setCurrentBlockState(3);
}
return;
}
if (previousBlockState() == 3)
{
QRegularExpressionMatch closeMatch = blockClose.match(phpText, 0);
@@ -391,18 +446,15 @@ void PhpHighlighter::highlightPhpRange(const QString &text, int start, int lengt
+ static_cast<int>(closeMatch.capturedLength());
setFormat(start, end, m_phpCommentFormat);
searchFrom = end;
// Block-Kommentar geschlossen — Zustand wird weiter unten gesetzt
}
else
{
// Gesamter Bereich ist noch Kommentar
setFormat(start, length, m_phpCommentFormat);
setCurrentBlockState(3);
return;
}
}
// Neue /* ... */ Kommentare innerhalb dieses PHP-Bereichs suchen
while (searchFrom < phpText.length())
{
QRegularExpressionMatch openMatch = blockOpen.match(phpText, searchFrom);
@@ -423,7 +475,6 @@ void PhpHighlighter::highlightPhpRange(const QString &text, int start, int lengt
}
else
{
// Kein schließendes */ gefunden — geht über Zeilenende
setFormat(start + openPos, length - openPos, m_phpCommentFormat);
setCurrentBlockState(3);
return;
@@ -434,13 +485,39 @@ void PhpHighlighter::highlightPhpRange(const QString &text, int start, int lengt
void PhpHighlighter::highlightBlock(const QString &text)
{
// Zuerst HTML-Basis-Regeln auf den gesamten Text anwenden
// (setzt auch den State für HTML <!-- --> Kommentare)
SyntaxHighlighter::highlightBlock(text);
// Block-Zustände:
// 0 = HTML-Modus
// 1 = HTML <!-- --> Kommentar (von Basisklasse verwaltet)
// 2 = innerhalb PHP-Block (kein /* */ Kommentar)
// 3 = innerhalb PHP /* */ Block-Kommentar
// State 1 (HTML-Kommentar) wurde von der Basisklasse gesetzt — nicht überschreiben
if (currentBlockState() == 1)
{
return;
}
// Wenn der vorherige Block ein HTML-Kommentar war und dieser noch nicht
// abgeschlossen wurde, hat die Basisklasse das bereits korrekt behandelt.
// Wir setzen nur dann auf 0 zurück wenn wir sicher nicht in HTML-Kommentar sind.
if (previousBlockState() != 1)
{
setCurrentBlockState(0);
}
// Leere Zeile — Zustand weitertragen
if (text.isEmpty())
{
const int prev = previousBlockState();
if (prev == 2 || prev == 3)
{
setCurrentBlockState(prev);
}
return;
}
static const QRegularExpression phpOpen(R"(<\?(?:php|=)?\s?)",
QRegularExpression::CaseInsensitiveOption);
@@ -466,8 +543,6 @@ void PhpHighlighter::highlightBlock(const QString &text)
else
{
highlightPhpRange(text, 0, text.length());
// highlightPhpRange setzt den Zustand auf 3 falls nötig,
// sonst behalten wir 2 (offener PHP-Block ohne Kommentar)
if (currentBlockState() != 3)
{
setCurrentBlockState(2);

View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE" sourcelanguage="de_DE">
<context>
<name>MainWindow</name>
<message><source>&amp;Datei</source><translation>&amp;Datei</translation></message>
<message><source>&amp;Neue Datei</source><translation>&amp;Neue Datei</translation></message>
<message><source>Datei &amp;öffnen</source><translation>Datei &amp;öffnen</translation></message>
<message><source>&amp;Projekt öffnen</source><translation>&amp;Projekt öffnen</translation></message>
<message><source>Projekt &amp;schließen</source><translation>Projekt &amp;schließen</translation></message>
<message><source>&amp;Speichern</source><translation>&amp;Speichern</translation></message>
<message><source>Speichern &amp;unter</source><translation>Speichern &amp;unter</translation></message>
<message><source>&amp;Alles speichern</source><translation>&amp;Alles speichern</translation></message>
<message><source>&amp;Beenden</source><translation>&amp;Beenden</translation></message>
<message><source>&amp;Bearbeiten</source><translation>&amp;Bearbeiten</translation></message>
<message><source>&amp;Rückgängig</source><translation>&amp;Rückgängig</translation></message>
<message><source>&amp;Wiederholen</source><translation>&amp;Wiederholen</translation></message>
<message><source>&amp;Suchen / Ersetzen</source><translation>&amp;Suchen / Ersetzen</translation></message>
<message><source>In &amp;Dateien suchen</source><translation>In &amp;Dateien suchen</translation></message>
<message><source>&amp;Projektfunktionen</source><translation>&amp;Projektfunktionen</translation></message>
<message><source>&amp;Tote Funktionen suchen</source><translation>&amp;Tote Funktionen suchen</translation></message>
<message><source>&amp;Ansicht</source><translation>&amp;Ansicht</translation></message>
<message><source>&amp;Dark Mode</source><translation>&amp;Dark Mode</translation></message>
<message><source>Sprache</source><translation>Sprache</translation></message>
<message><source>&amp;Hilfe</source><translation>&amp;Hilfe</translation></message>
<message><source>&amp;Über BareCode</source><translation>&amp;Über BareCode</translation></message>
<message><source>Bereit</source><translation>Bereit</translation></message>
<message><source>Alle Dateien gespeichert</source><translation>Alle Dateien gespeichert</translation></message>
<message><source>Gespeichert: %1</source><translation>Gespeichert: %1</translation></message>
<message><source>Projekt geöffnet: %1</source><translation>Projekt geöffnet: %1</translation></message>
<message><source>Projekt geschlossen</source><translation>Projekt geschlossen</translation></message>
<message><source>Sprache geändert</source><translation>Sprache geändert</translation></message>
<message><source>Die Sprache wird beim nächsten Start von BareCode aktiv.</source><translation>Die Sprache wird beim nächsten Start von BareCode aktiv.</translation></message>
</context>
<context>
<name>AboutDialog</name>
<message><source>Über BareCode</source><translation>Über BareCode</translation></message>
<message><source>Modularer Code-Editor</source><translation>Modularer Code-Editor</translation></message>
<message><source>Version</source><translation>Version</translation></message>
<message><source>Entwickler</source><translation>Entwickler</translation></message>
<message><source>Projekt</source><translation>Projekt</translation></message>
<message><source>Framework</source><translation>Framework</translation></message>
<message><source>Sprache</source><translation>Sprache</translation></message>
<message><source>Schließen</source><translation>Schließen</translation></message>
</context>
<context>
<name>FileTreePanel</name>
<message><source>Kein Projekt geöffnet</source><translation>Kein Projekt geöffnet</translation></message>
<message><source>Neue Datei</source><translation>Neue Datei</translation></message>
<message><source>Neuer Ordner</source><translation>Neuer Ordner</translation></message>
<message><source>Löschen</source><translation>Löschen</translation></message>
<message><source>Neue Datei</source><translation>Neue Datei</translation></message>
<message><source>Dateiname:</source><translation>Dateiname:</translation></message>
<message><source>Eine Datei mit diesem Namen existiert bereits:
%1</source><translation>Eine Datei mit diesem Namen existiert bereits:
%1</translation></message>
<message><source>Datei konnte nicht angelegt werden:
%1</source><translation>Datei konnte nicht angelegt werden:
%1</translation></message>
<message><source>Neuer Ordner</source><translation>Neuer Ordner</translation></message>
<message><source>Ordnername:</source><translation>Ordnername:</translation></message>
<message><source>Ordner konnte nicht angelegt werden:
%1</source><translation>Ordner konnte nicht angelegt werden:
%1</translation></message>
<message><source>%1 löschen</source><translation>%1 löschen</translation></message>
<message><source>%1 wirklich löschen?
%2</source><translation>%1 wirklich löschen?
%2</translation></message>
<message><source>Datei</source><translation>Datei</translation></message>
<message><source>Ordner</source><translation>Ordner</translation></message>
</context>
<context>
<name>EditorTab</name>
<message><source>Speichern</source><translation>Speichern</translation></message>
<message><source>Datei konnte nicht gespeichert werden:
%1</source><translation>Datei konnte nicht gespeichert werden:
%1</translation></message>
<message><source>Speichern unter</source><translation>Speichern unter</translation></message>
<message><source>Datei konnte nicht geschrieben werden:
%1</source><translation>Datei konnte nicht geschrieben werden:
%1</translation></message>
</context>
<context>
<name>CodeEditor</name>
<message><source>Datei öffnen</source><translation>Datei öffnen</translation></message>
<message><source>Open File</source><translation>Datei öffnen</translation></message>
<message><source>Cannot open file:
%1</source><translation>Datei kann nicht geöffnet werden:
%1</translation></message>
</context>
<context>
<name>SearchPanel</name>
<message><source>Suchen</source><translation>Suchen</translation></message>
<message><source>Suchen:</source><translation>Suchen:</translation></message>
<message><source></source><translation></translation></message>
<message><source></source><translation></translation></message>
<message><source>Vorheriger Treffer (Shift+F3)</source><translation>Vorheriger Treffer (Shift+F3)</translation></message>
<message><source>Nächster Treffer (F3)</source><translation>Nächster Treffer (F3)</translation></message>
<message><source>Schließen (Esc)</source><translation>Schließen (Esc)</translation></message>
<message><source></source><translation></translation></message>
<message><source>Ersetzen durch</source><translation>Ersetzen durch</translation></message>
<message><source>Ersetzen:</source><translation>Ersetzen:</translation></message>
<message><source>Ersetzen</source><translation>Ersetzen</translation></message>
<message><source>Alle ersetzen</source><translation>Alle ersetzen</translation></message>
<message><source>In Auswahl ersetzen</source><translation>In Auswahl ersetzen</translation></message>
<message><source>Groß-/Kleinschreibung</source><translation>Groß-/Kleinschreibung</translation></message>
<message><source>Ganzes Wort</source><translation>Ganzes Wort</translation></message>
<message><source>Regulärer Ausdruck</source><translation>Regulärer Ausdruck</translation></message>
<message><source>Kein Treffer</source><translation>Kein Treffer</translation></message>
<message><source>%1 Treffer</source><translation>%1 Treffer</translation></message>
<message><source>%1 Ersetzung(en) durchgeführt.</source><translation>%1 Ersetzung(en) durchgeführt.</translation></message>
<message><source>Es ist kein Text ausgewählt.</source><translation>Es ist kein Text ausgewählt.</translation></message>
<message><source>%1 Ersetzung(en) in der Auswahl durchgeführt.</source><translation>%1 Ersetzung(en) in der Auswahl durchgeführt.</translation></message>
</context>
<context>
<name>FileSearchPanel</name>
<message><source>Suchen in Dateien:</source><translation>Suchen in Dateien:</translation></message>
<message><source>Suchbegriff</source><translation>Suchbegriff</translation></message>
<message><source>Suchen</source><translation>Suchen</translation></message>
<message><source></source><translation></translation></message>
<message><source>Schließen</source><translation>Schließen</translation></message>
<message><source>Groß-/Kleinschreibung</source><translation>Groß-/Kleinschreibung</translation></message>
<message><source>Ganzes Wort</source><translation>Ganzes Wort</translation></message>
<message><source>Regex</source><translation>Regex</translation></message>
<message><source>Dateitypen:</source><translation>Dateitypen:</translation></message>
<message><source>Leerzeichen-getrennte Muster, z.B.: *.php *.html</source><translation>Leerzeichen-getrennte Muster, z.B.: *.php *.html</translation></message>
<message><source>Suche läuft</source><translation>Suche läuft</translation></message>
<message><source>Kein Projektverzeichnis geöffnet.</source><translation>Kein Projektverzeichnis geöffnet.</translation></message>
<message><source>Keine Treffer gefunden.</source><translation>Keine Treffer gefunden.</translation></message>
<message><source>%1 Treffer in %2 Datei(en).</source><translation>%1 Treffer in %2 Datei(en).</translation></message>
</context>
<context>
<name>FunctionListPanel</name>
<message><source>Projektfunktionen</source><translation>Projektfunktionen</translation></message>
<message><source></source><translation></translation></message>
<message><source>Neu scannen</source><translation>Neu scannen</translation></message>
<message><source>Funktion suchen</source><translation>Funktion suchen</translation></message>
<message><source>Nach Datei</source><translation>Nach Datei</translation></message>
<message><source>Nach Klasse</source><translation>Nach Klasse</translation></message>
<message><source>Alphabetisch</source><translation>Alphabetisch</translation></message>
<message><source>Kein Projekt geöffnet.</source><translation>Kein Projekt geöffnet.</translation></message>
<message><source>Scanne</source><translation>Scanne</translation></message>
<message><source>%1 Funktionen gefunden</source><translation>%1 Funktionen gefunden</translation></message>
<message><source>%1 von %2 Funktionen</source><translation>%1 von %2 Funktionen</translation></message>
<message><source>(global)</source><translation>(global)</translation></message>
</context>
<context>
<name>FunctionListDialog</name>
<message><source>Projektfunktionen BareCode</source><translation>Projektfunktionen BareCode</translation></message>
</context>
<context>
<name>DeadCodeAnalyzer</name>
<message><source>Globale Funktion kein Aufruf gefunden</source><translation>Globale Funktion kein Aufruf gefunden</translation></message>
<message><source>Methode von %1 kein Aufruf gefunden</source><translation>Methode von %1 kein Aufruf gefunden</translation></message>
</context>
<context>
<name>DeadCodeDialog</name>
<message><source>Tote Funktionen BareCode</source><translation>Tote Funktionen BareCode</translation></message>
<message><source>Kandidatenliste kein Aufruf per Regex gefunden. Dynamische Aufrufe (call_user_func, Strings, Hooks) werden nicht erkannt. Bitte vor dem Löschen manuell prüfen.</source><translation>Kandidatenliste kein Aufruf per Regex gefunden. Dynamische Aufrufe (call_user_func, Strings, Hooks) werden nicht erkannt. Bitte vor dem Löschen manuell prüfen.</translation></message>
<message><source>Ausschließen:</source><translation>Ausschließen:</translation></message>
<message><source>vendor lib node_modules (leerzeichen-getrennt)</source><translation>vendor lib node_modules (leerzeichen-getrennt)</translation></message>
<message><source>Verzeichnis aus dem Projekt auswählen und zur Ausschlussliste hinzufügen</source><translation>Verzeichnis aus dem Projekt auswählen und zur Ausschlussliste hinzufügen</translation></message>
<message><source>+ Verzeichnis</source><translation>+ Verzeichnis</translation></message>
<message><source>Ausschlussliste leeren</source><translation>Ausschlussliste leeren</translation></message>
<message><source>Verzeichnis ausschließen</source><translation>Verzeichnis ausschließen</translation></message>
<message><source>Analyse starten</source><translation>Analyse starten</translation></message>
<message><source>Als TXT exportieren</source><translation>Als TXT exportieren</translation></message>
<message><source>Analysiere</source><translation>Analysiere</translation></message>
<message><source>Dateien werden gezählt</source><translation>Dateien werden gezählt</translation></message>
<message><source>(%1 / %2) %3</source><translation>(%1 / %2) %3</translation></message>
<message><source>Funktion</source><translation>Funktion</translation></message>
<message><source>Klasse</source><translation>Klasse</translation></message>
<message><source>Datei</source><translation>Datei</translation></message>
<message><source>Zeile</source><translation>Zeile</translation></message>
<message><source>(global)</source><translation>(global)</translation></message>
<message><source> Keine ungenutzten Funktionen gefunden.</source><translation> Keine ungenutzten Funktionen gefunden.</translation></message>
<message><source>%1 Kandidaten gefunden</source><translation>%1 Kandidaten gefunden</translation></message>
<message><source>Analyse</source><translation>Analyse</translation></message>
<message><source>Bitte zuerst ein Projekt öffnen.</source><translation>Bitte zuerst ein Projekt öffnen.</translation></message>
<message><source>Ergebnis exportieren</source><translation>Ergebnis exportieren</translation></message>
<message><source>Textdateien (*.txt)</source><translation>Textdateien (*.txt)</translation></message>
<message><source>Export</source><translation>Export</translation></message>
<message><source>Datei konnte nicht geschrieben werden:
%1</source><translation>Datei konnte nicht geschrieben werden:
%1</translation></message>
<message><source>Exportiert nach:
%1</source><translation>Exportiert nach:
%1</translation></message>
</context>
<context>
<name>ColorIndicator</name>
<message><source>Farbe wählen</source><translation>Farbe wählen</translation></message>
</context>
<context>
<name>SignatureTooltip</name>
<message><source>Farbe wählen</source><translation>Farbe wählen</translation></message>
</context>
</TS>

View File

@@ -0,0 +1,194 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US" sourcelanguage="de_DE">
<context>
<name>MainWindow</name>
<message><source>&amp;Datei</source><translation>&amp;File</translation></message>
<message><source>&amp;Neue Datei</source><translation>&amp;New File</translation></message>
<message><source>Datei &amp;öffnen</source><translation>&amp;Open File</translation></message>
<message><source>&amp;Projekt öffnen</source><translation>Open &amp;Project</translation></message>
<message><source>Projekt &amp;schließen</source><translation>&amp;Close Project</translation></message>
<message><source>&amp;Speichern</source><translation>&amp;Save</translation></message>
<message><source>Speichern &amp;unter</source><translation>Save &amp;As</translation></message>
<message><source>&amp;Alles speichern</source><translation>Save A&amp;ll</translation></message>
<message><source>&amp;Beenden</source><translation>&amp;Quit</translation></message>
<message><source>&amp;Bearbeiten</source><translation>&amp;Edit</translation></message>
<message><source>&amp;Rückgängig</source><translation>&amp;Undo</translation></message>
<message><source>&amp;Wiederholen</source><translation>&amp;Redo</translation></message>
<message><source>&amp;Suchen / Ersetzen</source><translation>&amp;Find / Replace</translation></message>
<message><source>In &amp;Dateien suchen</source><translation>Find in &amp;Files</translation></message>
<message><source>&amp;Projektfunktionen</source><translation>&amp;Project Functions</translation></message>
<message><source>&amp;Tote Funktionen suchen</source><translation>Find &amp;Dead Code</translation></message>
<message><source>&amp;Ansicht</source><translation>&amp;View</translation></message>
<message><source>&amp;Dark Mode</source><translation>&amp;Dark Mode</translation></message>
<message><source>Sprache</source><translation>Language</translation></message>
<message><source>&amp;Hilfe</source><translation>&amp;Help</translation></message>
<message><source>&amp;Über BareCode</source><translation>&amp;About BareCode</translation></message>
<message><source>Bereit</source><translation>Ready</translation></message>
<message><source>Alle Dateien gespeichert</source><translation>All files saved</translation></message>
<message><source>Gespeichert: %1</source><translation>Saved: %1</translation></message>
<message><source>Projekt geöffnet: %1</source><translation>Project opened: %1</translation></message>
<message><source>Projekt geschlossen</source><translation>Project closed</translation></message>
<message><source>Sprache geändert</source><translation>Language changed</translation></message>
<message><source>Die Sprache wird beim nächsten Start von BareCode aktiv.</source><translation>The language will be applied the next time BareCode starts.</translation></message>
</context>
<context>
<name>AboutDialog</name>
<message><source>Über BareCode</source><translation>About BareCode</translation></message>
<message><source>Modularer Code-Editor</source><translation>Modular Code Editor</translation></message>
<message><source>Version</source><translation>Version</translation></message>
<message><source>Entwickler</source><translation>Developer</translation></message>
<message><source>Projekt</source><translation>Project</translation></message>
<message><source>Framework</source><translation>Framework</translation></message>
<message><source>Sprache</source><translation>Language</translation></message>
<message><source>Schließen</source><translation>Close</translation></message>
</context>
<context>
<name>FileTreePanel</name>
<message><source>Kein Projekt geöffnet</source><translation>No project open</translation></message>
<message><source>Neue Datei</source><translation>New File</translation></message>
<message><source>Neuer Ordner</source><translation>New Folder</translation></message>
<message><source>Löschen</source><translation>Delete</translation></message>
<message><source>Neue Datei</source><translation>New File</translation></message>
<message><source>Dateiname:</source><translation>File name:</translation></message>
<message><source>Eine Datei mit diesem Namen existiert bereits:
%1</source><translation>A file with this name already exists:
%1</translation></message>
<message><source>Datei konnte nicht angelegt werden:
%1</source><translation>Could not create file:
%1</translation></message>
<message><source>Neuer Ordner</source><translation>New Folder</translation></message>
<message><source>Ordnername:</source><translation>Folder name:</translation></message>
<message><source>Ordner konnte nicht angelegt werden:
%1</source><translation>Could not create folder:
%1</translation></message>
<message><source>%1 löschen</source><translation>Delete %1</translation></message>
<message><source>%1 wirklich löschen?
%2</source><translation>Really delete %1?
%2</translation></message>
<message><source>Datei</source><translation>file</translation></message>
<message><source>Ordner</source><translation>folder</translation></message>
</context>
<context>
<name>EditorTab</name>
<message><source>Speichern</source><translation>Save</translation></message>
<message><source>Datei konnte nicht gespeichert werden:
%1</source><translation>Could not save file:
%1</translation></message>
<message><source>Speichern unter</source><translation>Save As</translation></message>
<message><source>Datei konnte nicht geschrieben werden:
%1</source><translation>Could not write file:
%1</translation></message>
</context>
<context>
<name>CodeEditor</name>
<message><source>Datei öffnen</source><translation>Open File</translation></message>
<message><source>Open File</source><translation>Open File</translation></message>
<message><source>Cannot open file:
%1</source><translation>Cannot open file:
%1</translation></message>
</context>
<context>
<name>SearchPanel</name>
<message><source>Suchen</source><translation>Search</translation></message>
<message><source>Suchen:</source><translation>Find:</translation></message>
<message><source></source><translation></translation></message>
<message><source></source><translation></translation></message>
<message><source>Vorheriger Treffer (Shift+F3)</source><translation>Previous match (Shift+F3)</translation></message>
<message><source>Nächster Treffer (F3)</source><translation>Next match (F3)</translation></message>
<message><source>Schließen (Esc)</source><translation>Close (Esc)</translation></message>
<message><source></source><translation></translation></message>
<message><source>Ersetzen durch</source><translation>Replace with</translation></message>
<message><source>Ersetzen:</source><translation>Replace:</translation></message>
<message><source>Ersetzen</source><translation>Replace</translation></message>
<message><source>Alle ersetzen</source><translation>Replace All</translation></message>
<message><source>In Auswahl ersetzen</source><translation>Replace in Selection</translation></message>
<message><source>Groß-/Kleinschreibung</source><translation>Case Sensitive</translation></message>
<message><source>Ganzes Wort</source><translation>Whole Word</translation></message>
<message><source>Regulärer Ausdruck</source><translation>Regular Expression</translation></message>
<message><source>Kein Treffer</source><translation>No matches</translation></message>
<message><source>%1 Treffer</source><translation>%1 matches</translation></message>
<message><source>%1 Ersetzung(en) durchgeführt.</source><translation>%1 replacement(s) made.</translation></message>
<message><source>Es ist kein Text ausgewählt.</source><translation>No text selected.</translation></message>
<message><source>%1 Ersetzung(en) in der Auswahl durchgeführt.</source><translation>%1 replacement(s) in selection.</translation></message>
</context>
<context>
<name>FileSearchPanel</name>
<message><source>Suchen in Dateien:</source><translation>Search in files:</translation></message>
<message><source>Suchbegriff</source><translation>Search term</translation></message>
<message><source>Suchen</source><translation>Search</translation></message>
<message><source></source><translation></translation></message>
<message><source>Schließen</source><translation>Close</translation></message>
<message><source>Groß-/Kleinschreibung</source><translation>Case Sensitive</translation></message>
<message><source>Ganzes Wort</source><translation>Whole Word</translation></message>
<message><source>Regex</source><translation>Regex</translation></message>
<message><source>Dateitypen:</source><translation>File types:</translation></message>
<message><source>Leerzeichen-getrennte Muster, z.B.: *.php *.html</source><translation>Space-separated patterns, e.g.: *.php *.html</translation></message>
<message><source>Suche läuft</source><translation>Searching</translation></message>
<message><source>Kein Projektverzeichnis geöffnet.</source><translation>No project directory open.</translation></message>
<message><source>Keine Treffer gefunden.</source><translation>No matches found.</translation></message>
<message><source>%1 Treffer in %2 Datei(en).</source><translation>%1 match(es) in %2 file(s).</translation></message>
</context>
<context>
<name>FunctionListPanel</name>
<message><source>Projektfunktionen</source><translation>Project Functions</translation></message>
<message><source></source><translation></translation></message>
<message><source>Neu scannen</source><translation>Rescan</translation></message>
<message><source>Funktion suchen</source><translation>Search function</translation></message>
<message><source>Nach Datei</source><translation>By File</translation></message>
<message><source>Nach Klasse</source><translation>By Class</translation></message>
<message><source>Alphabetisch</source><translation>Alphabetically</translation></message>
<message><source>Kein Projekt geöffnet.</source><translation>No project open.</translation></message>
<message><source>Scanne</source><translation>Scanning</translation></message>
<message><source>%1 Funktionen gefunden</source><translation>%1 functions found</translation></message>
<message><source>%1 von %2 Funktionen</source><translation>%1 of %2 functions</translation></message>
<message><source>(global)</source><translation>(global)</translation></message>
</context>
<context>
<name>FunctionListDialog</name>
<message><source>Projektfunktionen BareCode</source><translation>Project Functions BareCode</translation></message>
</context>
<context>
<name>DeadCodeAnalyzer</name>
<message><source>Globale Funktion kein Aufruf gefunden</source><translation>Global function no call found</translation></message>
<message><source>Methode von %1 kein Aufruf gefunden</source><translation>Method of %1 no call found</translation></message>
</context>
<context>
<name>DeadCodeDialog</name>
<message><source>Tote Funktionen BareCode</source><translation>Dead Code BareCode</translation></message>
<message><source>Kandidatenliste kein Aufruf per Regex gefunden. Dynamische Aufrufe (call_user_func, Strings, Hooks) werden nicht erkannt. Bitte vor dem Löschen manuell prüfen.</source><translation>Candidate list no call found via regex. Dynamic calls (call_user_func, strings, hooks) are not detected. Please verify manually before deleting.</translation></message>
<message><source>Ausschließen:</source><translation>Exclude:</translation></message>
<message><source>vendor lib node_modules (leerzeichen-getrennt)</source><translation>vendor lib node_modules (space-separated)</translation></message>
<message><source>Verzeichnis aus dem Projekt auswählen und zur Ausschlussliste hinzufügen</source><translation>Select a directory from the project to add to the exclusion list</translation></message>
<message><source>+ Verzeichnis</source><translation>+ Directory</translation></message>
<message><source>Ausschlussliste leeren</source><translation>Clear exclusion list</translation></message>
<message><source>Verzeichnis ausschließen</source><translation>Exclude Directory</translation></message>
<message><source>Analyse starten</source><translation>Start Analysis</translation></message>
<message><source>Als TXT exportieren</source><translation>Export as TXT</translation></message>
<message><source>Analysiere</source><translation>Analysing</translation></message>
<message><source>Dateien werden gezählt</source><translation>Counting files</translation></message>
<message><source>(%1 / %2) %3</source><translation>(%1 / %2) %3</translation></message>
<message><source>Funktion</source><translation>Function</translation></message>
<message><source>Klasse</source><translation>Class</translation></message>
<message><source>Datei</source><translation>File</translation></message>
<message><source>Zeile</source><translation>Line</translation></message>
<message><source>(global)</source><translation>(global)</translation></message>
<message><source> Keine ungenutzten Funktionen gefunden.</source><translation> No unused functions found.</translation></message>
<message><source>%1 Kandidaten gefunden</source><translation>%1 candidates found</translation></message>
<message><source>Analyse</source><translation>Analysis</translation></message>
<message><source>Bitte zuerst ein Projekt öffnen.</source><translation>Please open a project first.</translation></message>
<message><source>Ergebnis exportieren</source><translation>Export Result</translation></message>
<message><source>Textdateien (*.txt)</source><translation>Text files (*.txt)</translation></message>
<message><source>Export</source><translation>Export</translation></message>
<message><source>Datei konnte nicht geschrieben werden:
%1</source><translation>Could not write file:
%1</translation></message>
<message><source>Exportiert nach:
%1</source><translation>Exported to:
%1</translation></message>
</context>
<context>
<name>ColorIndicator</name>
<message><source>Farbe wählen</source><translation>Choose Color</translation></message>
</context>
</TS>

133
bauen.sh Executable file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# ---------------------------------------------------------------------------
# BareCode Build-Skript
# Verwendung: ./bauen.sh [release|debug|clean|install]
# ---------------------------------------------------------------------------
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
BUILD_TYPE="${1:-release}"
BUILD_DIR="build"
# Farben
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}==>${NC} $1"; }
success() { echo -e "${GREEN}==>${NC} $1"; }
warn() { echo -e "${YELLOW}==>${NC} $1"; }
error() { echo -e "${RED}==>${NC} $1"; exit 1; }
# ---------------------------------------------------------------------------
# clean
# ---------------------------------------------------------------------------
if [ "$BUILD_TYPE" = "clean" ]; then
info "Build-Verzeichnis wird gelöscht…"
rm -rf "$BUILD_DIR"
success "Fertig."
exit 0
fi
# ---------------------------------------------------------------------------
# Abhängigkeiten prüfen
# ---------------------------------------------------------------------------
info "Prüfe Abhängigkeiten…"
command -v cmake &>/dev/null || error "cmake nicht gefunden. Installation: sudo pacman -S cmake"
command -v ninja &>/dev/null || error "ninja nicht gefunden. Installation: sudo pacman -S ninja"
command -v lrelease &>/dev/null || \
command -v lrelease-qt6 &>/dev/null || \
error "lrelease nicht gefunden. Installation: sudo pacman -S qt6-tools"
# Qt6 prüfen
if ! pkg-config --exists Qt6Core 2>/dev/null; then
if ! cmake --find-package -DNAME=Qt6 -DCOMPILER_ID=GNU \
-DLANGUAGE=CXX -DMODE=EXIST &>/dev/null 2>&1; then
warn "Qt6 konnte nicht automatisch geprüft werden — cmake wird es versuchen."
fi
fi
success "Abhängigkeiten OK."
# ---------------------------------------------------------------------------
# CMake-Build-Typ
# ---------------------------------------------------------------------------
case "$BUILD_TYPE" in
release|Release)
CMAKE_BUILD_TYPE="Release"
;;
debug|Debug)
CMAKE_BUILD_TYPE="Debug"
;;
install)
CMAKE_BUILD_TYPE="Release"
;;
*)
error "Unbekannter Build-Typ: '$BUILD_TYPE'. Erlaubt: release, debug, clean, install"
;;
esac
# ---------------------------------------------------------------------------
# Konfigurieren
# ---------------------------------------------------------------------------
info "Konfiguriere mit CMake (${CMAKE_BUILD_TYPE})…"
cmake -B "$BUILD_DIR" \
-G Ninja \
-DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
2>&1 || error "CMake-Konfiguration fehlgeschlagen."
success "Konfiguration OK."
# ---------------------------------------------------------------------------
# Bauen
# ---------------------------------------------------------------------------
JOBS=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)
info "Baue mit $JOBS parallelen Jobs…"
cmake --build "$BUILD_DIR" -j"$JOBS" 2>&1 || error "Build fehlgeschlagen."
success "Build erfolgreich!"
# ---------------------------------------------------------------------------
# Installieren (optional)
# ---------------------------------------------------------------------------
if [ "$BUILD_TYPE" = "install" ]; then
info "Installiere nach /usr (sudo erforderlich)…"
sudo cmake --install "$BUILD_DIR" || error "Installation fehlgeschlagen."
success "BareCode wurde installiert."
echo ""
echo " Starten: BareCode"
exit 0
fi
# ---------------------------------------------------------------------------
# Fertig
# ---------------------------------------------------------------------------
BINARY="$BUILD_DIR/BareCode"
if [ -f "$BINARY" ]; then
# .qm-Dateien neben die Binary kopieren damit sie gefunden werden
mkdir -p "$BUILD_DIR/translations"
if compgen -G "$BUILD_DIR/translations/barecode_*.qm" > /dev/null 2>&1; then
success "Übersetzungen bereits vorhanden."
else
# Aus dem cmake-Zwischenverzeichnis holen
find "$BUILD_DIR" -name "barecode_*.qm" -not -path "$BUILD_DIR/translations/*" \
-exec cp {} "$BUILD_DIR/translations/" \; 2>/dev/null || true
fi
echo ""
success "BareCode ist bereit:"
echo " Starten: ./$BINARY"
echo " Installieren: ./bauen.sh install"
echo " Debug-Build: ./bauen.sh debug"
echo " Aufräumen: ./bauen.sh clean"
fi

View File

@@ -1,5 +1,11 @@
#include <QApplication>
#include <QIcon>
#include <QTranslator>
#include <QLocale>
#include <QLibraryInfo>
#include <QSettings>
#include <QDir>
#include <QCoreApplication>
#include "core/MainWindow.h"
int main(int argc, char *argv[])
@@ -7,10 +13,10 @@ int main(int argc, char *argv[])
QApplication app(argc, argv);
app.setApplicationName("BareCode");
app.setApplicationVersion("1.2.0");
app.setApplicationVersion("1.3.0");
app.setOrganizationName("BareCode");
// Icon in allen verfügbaren Größen setzen
// Icon
QIcon appIcon;
appIcon.addFile(":/icon_16.png", QSize(16, 16));
appIcon.addFile(":/icon_32.png", QSize(32, 32));
@@ -21,8 +27,80 @@ int main(int argc, char *argv[])
appIcon.addFile(":/icon_512.png", QSize(512, 512));
app.setWindowIcon(appIcon);
// ---------------------------------------------------------------------------
// Übersetzung laden
// .qm-Dateien liegen im Unterverzeichnis "translations" neben der Binary
// oder unter /usr/share/BareCode/translations nach Installation
// ---------------------------------------------------------------------------
QSettings settings(QSettings::IniFormat, QSettings::UserScope,
"BareCode", "BareCode");
QString locale = settings.value("language/locale", QString()).toString();
if (locale.isEmpty())
{
locale = QLocale::system().name(); // z.B. "de_DE", "en_US"
}
const QString shortLocale = locale.left(2); // "de", "en"
// Suchpfade für .qm-Dateien
QStringList searchPaths;
searchPaths << QCoreApplication::applicationDirPath() + "/translations"
<< QDir::homePath() + "/.local/share/BareCode/translations"
<< "/usr/share/BareCode/translations"
<< "/usr/local/share/BareCode/translations";
// Qt-eigene Übersetzungen (Dialoge, Standard-Buttons)
QTranslator qtTranslator;
if (qtTranslator.load("qt_" + locale,
QLibraryInfo::path(QLibraryInfo::TranslationsPath)))
{
app.installTranslator(&qtTranslator);
}
// BareCode-Übersetzung — in allen Suchpfaden versuchen
QTranslator appTranslator;
bool loaded = false;
for (const QString &path : searchPaths)
{
if (appTranslator.load(QString("barecode_%1").arg(locale), path) ||
appTranslator.load(QString("barecode_%1").arg(shortLocale), path))
{
app.installTranslator(&appTranslator);
loaded = true;
break;
}
}
// Fallback: Englisch
if (!loaded && shortLocale != "de")
{
for (const QString &path : searchPaths)
{
if (appTranslator.load("barecode_en", path))
{
app.installTranslator(&appTranslator);
break;
}
}
}
MainWindow window;
window.show();
// Datei(en) öffnen, die beim Start per Kommandozeile übergeben wurden
// (z. B. Doppelklick auf eine Datei im Dateibrowser → "Öffnen mit BareCode").
// Das erste Argument ist der Programmname selbst und wird übersprungen.
QStringList cliFiles = app.arguments();
if (!cliFiles.isEmpty())
{
cliFiles.removeFirst();
}
if (!cliFiles.isEmpty())
{
window.openFilesFromArguments(cliFiles);
}
return app.exec();
}

View File

@@ -1 +0,0 @@
/home/diabolus/Arbeit/Projekt-Hirnfrei/BareCode/barecode-1.1.0.tar.gz

View File

@@ -1,47 +0,0 @@
# Maintainer: Dany Thinnes <deine@email.de>
pkgname=barecode-git
pkgver=r5.cb66172
pkgrel=1
pkgdesc="Modularer Code-Editor, entwickelt von Projekt Hirnfrei (git)"
arch=('x86_64' 'aarch64')
url="https://git.projekt-hirnfrei.de/diabolus/BareCode"
license=('MIT')
depends=('qt6-base')
makedepends=('cmake' 'ninja' 'git')
provides=('barecode')
conflicts=('barecode')
source=("$pkgname::git+$url.git")
sha256sums=('SKIP')
pkgver()
{
cd "$pkgname"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build()
{
cmake \
-B build \
-S "$pkgname" \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr
cmake --build build
}
package()
{
DESTDIR="$pkgdir" cmake --install build
install -Dm644 "$pkgname/barecode.desktop" \
"$pkgdir/usr/share/applications/barecode.desktop"
# install -Dm644 "$pkgname/resources/barecode.png" \
# "$pkgdir/usr/share/pixmaps/barecode.png"
install -Dm644 "$pkgname/LICENSE" \
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

View File

@@ -1,47 +0,0 @@
# Maintainer: Dany Thinnes <deine@email.de>
pkgname=barecode-git
pkgver=r1.0.0
pkgrel=1
pkgdesc="Modularer Code-Editor, entwickelt von Projekt Hirnfrei (git)"
arch=('x86_64' 'aarch64')
url="https://git.projekt-hirnfrei.de/diabolus/BareCode"
license=('MIT')
depends=('qt6-base')
makedepends=('cmake' 'ninja' 'git')
provides=('barecode')
conflicts=('barecode')
source=("$pkgname::git+$url.git")
sha256sums=('SKIP')
pkgver()
{
cd "$pkgname"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build()
{
cmake \
-B build \
-S "$pkgname" \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr
cmake --build build
}
package()
{
DESTDIR="$pkgdir" cmake --install build
install -Dm644 "$pkgname/barecode.desktop" \
"$pkgdir/usr/share/applications/barecode.desktop"
# install -Dm644 "$pkgname/resources/barecode.png" \
# "$pkgdir/usr/share/pixmaps/barecode.png"
install -Dm644 "$pkgname/LICENSE" \
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

View File

@@ -1,41 +0,0 @@
# Maintainer: Dany Thinnes <deine@email.de>
pkgname=barecode
pkgver=1.0.0
pkgrel=1
pkgdesc="Modularer Code-Editor, entwickelt von Projekt Hirnfrei"
arch=('x86_64' 'aarch64')
url="https://git.projekt-hirnfrei.de/diabolus/BareCode"
license=('MIT')
depends=('qt6-base')
makedepends=('cmake' 'ninja' 'git')
# Gitea erzeugt automatisch Tarballs unter:
# https://DEIN-GITEA-SERVER/DEINNAME/BareCode/archive/v1.0.0.tar.gz
#
# sha256sum nach dem Taggen ermitteln:
# curl -L https://DEIN-GITEA-SERVER/DEINNAME/BareCode/archive/v1.0.0.tar.gz | sha256sum
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
sha256sums=('SKIP') # Ersetzen nach erstem Tag
build()
{
cmake \
-B build \
-S "BareCode-$pkgver" \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr
cmake --build build
}
package()
{
DESTDIR="$pkgdir" cmake --install build
install -Dm644 "BareCode-$pkgver/barecode.desktop" \
"$pkgdir/usr/share/applications/barecode.desktop"
install -Dm644 "BareCode-$pkgver/LICENSE" \
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

View File

@@ -1 +0,0 @@
ref: refs/heads/main

View File

@@ -1,9 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true
[remote "origin"]
url = https://git.projekt-hirnfrei.de/diabolus/BareCode.git
tagOpt = --no-tags
fetch = +refs/*:refs/*
mirror = true

View File

@@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -1,15 +0,0 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

View File

@@ -1,74 +0,0 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines and messages that
# would confuse 'git am'.
ret=0
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
ret=1
}
comment_re="$(
{
git config --get-regexp "^core\.comment(char|string)\$" ||
echo '#'
} | sed -n -e '
${
s/^[^ ]* //
s|[][*./\]|\\&|g
s/^auto$/[#;@!$%^&|:]/
p
}'
)"
scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$"
comment_line="^${comment_re}.*"
blank_line='^[ ]*$'
# Disallow lines starting with "diff -" or "Index: " in the body of the
# message. Stop looking if we see a scissors line.
line="$(sed -n -e "
# Skip comments and blank lines at the start of the file.
/${scissors_line}/q
/${comment_line}/d
/${blank_line}/d
# The first paragraph will become the subject header so
# does not need to be checked.
: subject
n
/${scissors_line}/q
/${blank_line}/!b subject
# Check the body of the message for problematic
# prefixes.
: body
n
/${scissors_line}/q
/${comment_line}/b body
/^diff -/{p;q;}
/^Index: /{p;q;}
b body
" "$1")"
if test -n "$line"
then
echo >&2 "Message contains a diff that will confuse 'git am'."
echo >&2 "To fix this indent the diff."
ret=1
fi
exit $ret

View File

@@ -1,168 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
# An example hook script to integrate Watchman
# (https://facebook.github.io/watchman/) with git to speed up detecting
# new and modified files.
#
# The hook is passed a version (currently 2) and last update token
# formatted as a string and outputs to stdout a new update token and
# all files that have been modified since the update token. Paths must
# be relative to the root of the working tree and separated by a single NUL.
#
# To enable this hook, rename this file to "query-watchman" and set
# 'git config core.fsmonitor .git/hooks/query-watchman'
#
my ($version, $last_update_token) = @ARGV;
# Uncomment for debugging
# print STDERR "$0 $version $last_update_token\n";
# Check the hook interface version
if ($version ne 2) {
die "Unsupported query-fsmonitor hook version '$version'.\n" .
"Falling back to scanning...\n";
}
my $git_work_tree = get_working_dir();
my $json_pkg;
eval {
require JSON::XS;
$json_pkg = "JSON::XS";
1;
} or do {
require JSON::PP;
$json_pkg = "JSON::PP";
};
launch_watchman();
sub launch_watchman {
my $o = watchman_query();
if (is_work_tree_watched($o)) {
output_result($o->{clock}, @{$o->{files}});
}
}
sub output_result {
my ($clockid, @files) = @_;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# binmode $fh, ":utf8";
# print $fh "$clockid\n@files\n";
# close $fh;
binmode STDOUT, ":utf8";
print $clockid;
print "\0";
local $, = "\0";
print @files;
}
sub watchman_clock {
my $response = qx/watchman clock "$git_work_tree"/;
die "Failed to get clock id on '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
return $json_pkg->new->utf8->decode($response);
}
sub watchman_query {
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
or die "open2() failed: $!\n" .
"Falling back to scanning...\n";
# In the query expression below we're asking for names of files that
# changed since $last_update_token but not from the .git folder.
#
# To accomplish this, we're using the "since" generator to use the
# recency index to select candidate nodes and "fields" to limit the
# output to file names only. Then we're using the "expression" term to
# further constrain the results.
my $last_update_line = "";
if (substr($last_update_token, 0, 1) eq "c") {
$last_update_token = "\"$last_update_token\"";
$last_update_line = qq[\n"since": $last_update_token,];
}
my $query = <<" END";
["query", "$git_work_tree", {$last_update_line
"fields": ["name"],
"expression": ["not", ["dirname", ".git"]]
}]
END
# Uncomment for debugging the watchman query
# open (my $fh, ">", ".git/watchman-query.json");
# print $fh $query;
# close $fh;
print CHLD_IN $query;
close CHLD_IN;
my $response = do {local $/; <CHLD_OUT>};
# Uncomment for debugging the watch response
# open ($fh, ">", ".git/watchman-response.json");
# print $fh $response;
# close $fh;
die "Watchman: command returned no output.\n" .
"Falling back to scanning...\n" if $response eq "";
die "Watchman: command returned invalid output: $response\n" .
"Falling back to scanning...\n" unless $response =~ /^\{/;
return $json_pkg->new->utf8->decode($response);
}
sub is_work_tree_watched {
my ($output) = @_;
my $error = $output->{error};
if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
my $response = qx/watchman watch "$git_work_tree"/;
die "Failed to make watchman watch '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
$output = $json_pkg->new->utf8->decode($response);
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# close $fh;
# Watchman will always return all files on the first query so
# return the fast "everything is dirty" flag to git and do the
# Watchman query just to get it over with now so we won't pay
# the cost in git to look up each individual file.
my $o = watchman_clock();
$error = $o->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
output_result($o->{clock}, ("/"));
return 0;
}
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
return 1;
}
sub get_working_dir {
my $working_dir;
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
$working_dir = Win32::GetCwd();
$working_dir =~ tr/\\/\//;
} else {
require Cwd;
$working_dir = Cwd::cwd();
}
return $working_dir;
}

View File

@@ -1,8 +0,0 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info

View File

@@ -1,14 +0,0 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

View File

@@ -1,49 +0,0 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff-index --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

View File

@@ -1,13 +0,0 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
:

View File

@@ -1,53 +0,0 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
while read local_ref local_oid remote_ref remote_oid
do
if test "$local_oid" = "$zero"
then
# Handle delete
:
else
if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
range="$local_oid"
else
# Update to existing branch, examine new commits
range="$remote_oid..$local_oid"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0

View File

@@ -1,169 +0,0 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END

View File

@@ -1,24 +0,0 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi

Some files were not shown because too many files have changed in this diff Show More