mirror of
https://github.com/johndoe6345789/WizardMerge.git
synced 2026-04-24 13:44:55 +00:00
Create multi-frontend architecture with qt6, nextjs, and cli frontends
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
184
frontends/README.md
Normal file
184
frontends/README.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# WizardMerge Frontends
|
||||
|
||||
This directory contains multiple frontend implementations for WizardMerge, each designed for different use cases and workflows.
|
||||
|
||||
## Available Frontends
|
||||
|
||||
### 1. Qt6 Desktop Frontend (`qt6/`)
|
||||
|
||||
**Type**: Native desktop application
|
||||
**Language**: C++ with Qt6 and QML
|
||||
**Platforms**: Linux, Windows, macOS
|
||||
|
||||
A native desktop application providing the best performance and integration with desktop environments.
|
||||
|
||||
**Features**:
|
||||
- Native window management and desktop integration
|
||||
- Offline capability with embedded backend option
|
||||
- High-performance rendering
|
||||
- Three-panel diff viewer with QML-based UI
|
||||
- Keyboard shortcuts and native file dialogs
|
||||
|
||||
**Best for**: Desktop users who want a fast, native application with full offline support.
|
||||
|
||||
See [qt6/README.md](qt6/README.md) for build and usage instructions.
|
||||
|
||||
### 2. Next.js Web Frontend (`nextjs/`)
|
||||
|
||||
**Type**: Web application
|
||||
**Language**: TypeScript with React/Next.js
|
||||
**Runtime**: bun
|
||||
|
||||
A modern web-based interface accessible from any browser.
|
||||
|
||||
**Features**:
|
||||
- Cross-platform browser access
|
||||
- No installation required
|
||||
- Real-time collaboration (planned)
|
||||
- Responsive design
|
||||
- Easy deployment and updates
|
||||
|
||||
**Best for**: Teams needing shared access, cloud deployments, or users who prefer web interfaces.
|
||||
|
||||
See [nextjs/README.md](nextjs/README.md) for development and deployment instructions.
|
||||
|
||||
### 3. CLI Frontend (`cli/`)
|
||||
|
||||
**Type**: Command-line interface
|
||||
**Language**: C++
|
||||
**Platforms**: Linux, Windows, macOS
|
||||
|
||||
A command-line tool for automation and scripting.
|
||||
|
||||
**Features**:
|
||||
- Non-interactive batch processing
|
||||
- Scriptable and automatable
|
||||
- CI/CD pipeline integration
|
||||
- Git workflow integration
|
||||
- Minimal dependencies
|
||||
|
||||
**Best for**: Automation, scripting, CI/CD pipelines, and terminal-based workflows.
|
||||
|
||||
See [cli/README.md](cli/README.md) for usage and examples.
|
||||
|
||||
## Architecture
|
||||
|
||||
All frontends communicate with the WizardMerge C++ backend through a common HTTP API:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Frontends │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌──────────┐ │
|
||||
│ │ Qt6 Native │ │ Next.js │ │ CLI │ │
|
||||
│ │ (C++) │ │(TypeScript)│ │ (C++) │ │
|
||||
│ └─────┬──────┘ └──────┬─────┘ └────┬─────┘ │
|
||||
└────────┼─────────────────┼─────────────┼───────┘
|
||||
│ │ │
|
||||
└─────────────────┼─────────────┘
|
||||
│
|
||||
HTTP REST API
|
||||
│
|
||||
┌─────────────────▼──────────────────┐
|
||||
│ WizardMerge C++ Backend │
|
||||
│ (Drogon HTTP Server) │
|
||||
│ │
|
||||
│ - Three-way merge algorithm │
|
||||
│ - Conflict detection │
|
||||
│ - Auto-resolution │
|
||||
│ - Semantic analysis │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Backend API
|
||||
|
||||
The backend provides a REST API on port 8080 (configurable):
|
||||
|
||||
- `POST /api/merge` - Perform three-way merge
|
||||
|
||||
All frontends use this common API, ensuring consistent merge behavior regardless of the interface used.
|
||||
|
||||
## Choosing a Frontend
|
||||
|
||||
| Feature | Qt6 | Next.js | CLI |
|
||||
|---------|-----|---------|-----|
|
||||
| Native Performance | ✓ | - | ✓ |
|
||||
| Offline Support | ✓ | - | ✓ |
|
||||
| Web Browser Access | - | ✓ | - |
|
||||
| Collaboration | - | ✓ (planned) | - |
|
||||
| Automation/Scripting | - | - | ✓ |
|
||||
| Visual UI | ✓ | ✓ | - |
|
||||
| Installation Required | ✓ | - | ✓ |
|
||||
| Platform Support | All | All | All |
|
||||
|
||||
## Building All Frontends
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install dependencies for all frontends you want to build:
|
||||
|
||||
```bash
|
||||
# Qt6 (for qt6 frontend)
|
||||
# Ubuntu/Debian:
|
||||
sudo apt-get install qt6-base-dev qt6-declarative-dev
|
||||
|
||||
# macOS:
|
||||
brew install qt@6
|
||||
|
||||
# Next.js (for nextjs frontend)
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
|
||||
# CLI (for cli frontend)
|
||||
# Ubuntu/Debian:
|
||||
sudo apt-get install libcurl4-openssl-dev
|
||||
|
||||
# macOS:
|
||||
brew install curl
|
||||
```
|
||||
|
||||
### Build All
|
||||
|
||||
```bash
|
||||
# Build Qt6 frontend
|
||||
cd qt6
|
||||
mkdir build && cd build
|
||||
cmake .. -G Ninja
|
||||
ninja
|
||||
cd ../..
|
||||
|
||||
# Build/setup Next.js frontend
|
||||
cd nextjs
|
||||
bun install
|
||||
bun run build
|
||||
cd ..
|
||||
|
||||
# Build CLI frontend
|
||||
cd cli
|
||||
mkdir build && cd build
|
||||
cmake .. -G Ninja
|
||||
ninja
|
||||
cd ../..
|
||||
```
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
When developing a frontend:
|
||||
|
||||
1. **Consistency**: Maintain consistent UX across all frontends where applicable
|
||||
2. **API Usage**: Use the common backend API for all merge operations
|
||||
3. **Error Handling**: Properly handle backend connection errors and API failures
|
||||
4. **Documentation**: Update frontend-specific README files
|
||||
5. **Testing**: Add tests for new features
|
||||
|
||||
### Adding a New Frontend
|
||||
|
||||
To add a new frontend implementation:
|
||||
|
||||
1. Create a new directory under `frontends/`
|
||||
2. Implement the UI using your chosen technology
|
||||
3. Use the backend HTTP API (`POST /api/merge`)
|
||||
4. Add a README.md with build and usage instructions
|
||||
5. Update this file to list the new frontend
|
||||
|
||||
## License
|
||||
|
||||
See [../LICENSE](../LICENSE) for details.
|
||||
61
frontends/cli/CMakeLists.txt
Normal file
61
frontends/cli/CMakeLists.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(wizardmerge-cli-frontend VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Find libcurl
|
||||
find_package(CURL QUIET)
|
||||
|
||||
if(NOT CURL_FOUND)
|
||||
message(WARNING "libcurl not found. Skipping CLI frontend build.")
|
||||
message(WARNING "Install libcurl to build the CLI frontend:")
|
||||
message(WARNING " - Ubuntu/Debian: sudo apt-get install libcurl4-openssl-dev")
|
||||
message(WARNING " - macOS: brew install curl")
|
||||
message(WARNING " - Windows: Install via vcpkg or use system curl")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Source files
|
||||
set(SOURCES
|
||||
src/main.cpp
|
||||
src/http_client.cpp
|
||||
src/file_utils.cpp
|
||||
)
|
||||
|
||||
# Header files
|
||||
set(HEADERS
|
||||
include/http_client.h
|
||||
include/file_utils.h
|
||||
)
|
||||
|
||||
# Create executable
|
||||
add_executable(wizardmerge-cli-frontend
|
||||
${SOURCES}
|
||||
${HEADERS}
|
||||
)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(wizardmerge-cli-frontend PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CURL_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(wizardmerge-cli-frontend PRIVATE
|
||||
${CURL_LIBRARIES}
|
||||
)
|
||||
|
||||
# Compiler warnings
|
||||
if(MSVC)
|
||||
target_compile_options(wizardmerge-cli-frontend PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(wizardmerge-cli-frontend PRIVATE -Wall -Wextra -pedantic)
|
||||
endif()
|
||||
|
||||
# Install target
|
||||
install(TARGETS wizardmerge-cli-frontend
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
|
||||
message(STATUS "CLI frontend configured successfully")
|
||||
277
frontends/cli/README.md
Normal file
277
frontends/cli/README.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# WizardMerge CLI Frontend
|
||||
|
||||
Command-line interface for WizardMerge merge conflict resolution.
|
||||
|
||||
## Features
|
||||
|
||||
- Simple command-line interface
|
||||
- Communicates with WizardMerge backend via HTTP API
|
||||
- Suitable for automation and scripting
|
||||
- Cross-platform (Linux, Windows, macOS)
|
||||
- Non-interactive batch processing
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- C++17 compiler (GCC 7+, Clang 6+, MSVC 2017+)
|
||||
- CMake 3.15+
|
||||
- libcurl (for HTTP client)
|
||||
|
||||
## Building
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
**Ubuntu/Debian:**
|
||||
```bash
|
||||
sudo apt-get install libcurl4-openssl-dev
|
||||
```
|
||||
|
||||
**macOS (Homebrew):**
|
||||
```bash
|
||||
brew install curl
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
libcurl is typically included with MSVC or can be installed via vcpkg.
|
||||
|
||||
### Build the Application
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make
|
||||
```
|
||||
|
||||
Or using Ninja:
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
./wizardmerge-cli --help
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Three-Way Merge
|
||||
|
||||
```bash
|
||||
wizardmerge-cli merge --base base.txt --ours ours.txt --theirs theirs.txt -o result.txt
|
||||
```
|
||||
|
||||
### Merge with Backend Server
|
||||
|
||||
```bash
|
||||
# Use default backend (http://localhost:8080)
|
||||
wizardmerge-cli merge --base base.txt --ours ours.txt --theirs theirs.txt
|
||||
|
||||
# Specify custom backend URL
|
||||
wizardmerge-cli --backend http://remote-server:8080 merge --base base.txt --ours ours.txt --theirs theirs.txt
|
||||
```
|
||||
|
||||
### Git Integration
|
||||
|
||||
```bash
|
||||
# Resolve conflicts in a Git repository
|
||||
cd /path/to/git/repo
|
||||
wizardmerge-cli git-resolve
|
||||
|
||||
# Resolve a specific file
|
||||
wizardmerge-cli git-resolve path/to/conflicted/file.txt
|
||||
```
|
||||
|
||||
### Batch Processing
|
||||
|
||||
```bash
|
||||
# Process all conflicted files in current directory
|
||||
wizardmerge-cli batch-resolve .
|
||||
```
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Global Options
|
||||
|
||||
- `--backend <url>` - Backend server URL (default: http://localhost:8080)
|
||||
- `--verbose, -v` - Enable verbose output
|
||||
- `--quiet, -q` - Suppress non-error output
|
||||
- `--help, -h` - Show help message
|
||||
- `--version` - Show version information
|
||||
|
||||
### Commands
|
||||
|
||||
#### merge
|
||||
|
||||
Perform a three-way merge operation.
|
||||
|
||||
```bash
|
||||
wizardmerge-cli merge [OPTIONS]
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--base <file>` - Path to base version (required)
|
||||
- `--ours <file>` - Path to our version (required)
|
||||
- `--theirs <file>` - Path to their version (required)
|
||||
- `-o, --output <file>` - Output file path (default: stdout)
|
||||
- `--format <format>` - Output format: text, json (default: text)
|
||||
|
||||
#### git-resolve
|
||||
|
||||
Resolve Git merge conflicts.
|
||||
|
||||
```bash
|
||||
wizardmerge-cli git-resolve [FILE]
|
||||
```
|
||||
|
||||
Arguments:
|
||||
- `FILE` - Specific file to resolve (optional, resolves all if omitted)
|
||||
|
||||
#### batch-resolve
|
||||
|
||||
Batch process multiple files.
|
||||
|
||||
```bash
|
||||
wizardmerge-cli batch-resolve [DIRECTORY]
|
||||
```
|
||||
|
||||
Arguments:
|
||||
- `DIRECTORY` - Directory to scan for conflicts (default: current directory)
|
||||
|
||||
Options:
|
||||
- `--recursive, -r` - Process directories recursively
|
||||
- `--pattern <pattern>` - File pattern to match (default: *)
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Simple Merge
|
||||
|
||||
```bash
|
||||
# Create test files
|
||||
echo -e "line1\nline2\nline3" > base.txt
|
||||
echo -e "line1\nline2-ours\nline3" > ours.txt
|
||||
echo -e "line1\nline2-theirs\nline3" > theirs.txt
|
||||
|
||||
# Perform merge
|
||||
wizardmerge-cli merge --base base.txt --ours ours.txt --theirs theirs.txt
|
||||
```
|
||||
|
||||
### Example 2: JSON Output
|
||||
|
||||
```bash
|
||||
wizardmerge-cli merge --base base.txt --ours ours.txt --theirs theirs.txt --format json > result.json
|
||||
```
|
||||
|
||||
### Example 3: Git Workflow
|
||||
|
||||
```bash
|
||||
# In a Git repository with conflicts
|
||||
git merge feature-branch
|
||||
# Conflicts occur...
|
||||
|
||||
# Resolve using WizardMerge
|
||||
wizardmerge-cli git-resolve
|
||||
|
||||
# Review and commit
|
||||
git commit
|
||||
```
|
||||
|
||||
## Exit Codes
|
||||
|
||||
- `0` - Success (no conflicts or all conflicts resolved)
|
||||
- `1` - General error
|
||||
- `2` - Invalid arguments
|
||||
- `3` - Backend connection error
|
||||
- `4` - File I/O error
|
||||
- `5` - Merge conflicts detected (when running in strict mode)
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration can be provided via:
|
||||
|
||||
1. Command-line arguments (highest priority)
|
||||
2. Environment variables:
|
||||
- `WIZARDMERGE_BACKEND` - Backend server URL
|
||||
- `WIZARDMERGE_VERBOSE` - Enable verbose output (1/0)
|
||||
3. Configuration file `~/.wizardmergerc` (lowest priority)
|
||||
|
||||
### Configuration File Format
|
||||
|
||||
```ini
|
||||
[backend]
|
||||
url = http://localhost:8080
|
||||
|
||||
[cli]
|
||||
verbose = false
|
||||
format = text
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
cli/
|
||||
├── CMakeLists.txt # CMake build configuration
|
||||
├── README.md # This file
|
||||
├── src/ # C++ source files
|
||||
│ ├── main.cpp # Application entry point
|
||||
│ ├── http_client.cpp # HTTP client implementation
|
||||
│ └── file_utils.cpp # File handling utilities
|
||||
└── include/ # Header files
|
||||
├── http_client.h
|
||||
└── file_utils.h
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Architecture
|
||||
|
||||
The CLI frontend is a thin client that:
|
||||
1. Parses command-line arguments
|
||||
2. Reads input files
|
||||
3. Sends HTTP requests to backend
|
||||
4. Formats and displays results
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Standard C++ library
|
||||
- libcurl (for HTTP client)
|
||||
- POSIX API (for file operations)
|
||||
|
||||
### Adding New Commands
|
||||
|
||||
1. Add command handler in `src/main.cpp`
|
||||
2. Implement command logic
|
||||
3. Update help text and README
|
||||
4. Add tests
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Backend Connection Failed
|
||||
|
||||
```bash
|
||||
# Check backend is running
|
||||
curl http://localhost:8080/api/health
|
||||
|
||||
# Start backend if needed
|
||||
cd ../../backend
|
||||
./build/wizardmerge-cli
|
||||
```
|
||||
|
||||
### File Not Found
|
||||
|
||||
Ensure file paths are correct and files are readable:
|
||||
```bash
|
||||
ls -la base.txt ours.txt theirs.txt
|
||||
```
|
||||
|
||||
### Permission Denied
|
||||
|
||||
Check file permissions:
|
||||
```bash
|
||||
chmod +r base.txt ours.txt theirs.txt
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](../../LICENSE) for details.
|
||||
43
frontends/cli/include/file_utils.h
Normal file
43
frontends/cli/include/file_utils.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef FILE_UTILS_H
|
||||
#define FILE_UTILS_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief File utility functions
|
||||
*/
|
||||
class FileUtils {
|
||||
public:
|
||||
/**
|
||||
* @brief Read a file and split into lines
|
||||
* @param filePath Path to the file
|
||||
* @param lines Output vector of lines
|
||||
* @return true if successful, false on error
|
||||
*/
|
||||
static bool readLines(const std::string& filePath, std::vector<std::string>& lines);
|
||||
|
||||
/**
|
||||
* @brief Write lines to a file
|
||||
* @param filePath Path to the file
|
||||
* @param lines Vector of lines to write
|
||||
* @return true if successful, false on error
|
||||
*/
|
||||
static bool writeLines(const std::string& filePath, const std::vector<std::string>& lines);
|
||||
|
||||
/**
|
||||
* @brief Check if a file exists
|
||||
* @param filePath Path to the file
|
||||
* @return true if file exists, false otherwise
|
||||
*/
|
||||
static bool fileExists(const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief Get file size in bytes
|
||||
* @param filePath Path to the file
|
||||
* @return File size, or -1 on error
|
||||
*/
|
||||
static long getFileSize(const std::string& filePath);
|
||||
};
|
||||
|
||||
#endif // FILE_UTILS_H
|
||||
62
frontends/cli/include/http_client.h
Normal file
62
frontends/cli/include/http_client.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef HTTP_CLIENT_H
|
||||
#define HTTP_CLIENT_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* @brief HTTP client for communicating with WizardMerge backend
|
||||
*/
|
||||
class HttpClient {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct HTTP client with backend URL
|
||||
* @param backendUrl URL of the backend server (e.g., "http://localhost:8080")
|
||||
*/
|
||||
explicit HttpClient(const std::string& backendUrl);
|
||||
|
||||
/**
|
||||
* @brief Perform a three-way merge via backend API
|
||||
* @param base Base version lines
|
||||
* @param ours Our version lines
|
||||
* @param theirs Their version lines
|
||||
* @param merged Output merged lines
|
||||
* @param hasConflicts Output whether conflicts were detected
|
||||
* @return true if successful, false on error
|
||||
*/
|
||||
bool performMerge(
|
||||
const std::vector<std::string>& base,
|
||||
const std::vector<std::string>& ours,
|
||||
const std::vector<std::string>& theirs,
|
||||
std::vector<std::string>& merged,
|
||||
bool& hasConflicts
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Check if backend is reachable
|
||||
* @return true if backend responds, false otherwise
|
||||
*/
|
||||
bool checkBackend();
|
||||
|
||||
/**
|
||||
* @brief Get last error message
|
||||
* @return Error message string
|
||||
*/
|
||||
std::string getLastError() const { return lastError_; }
|
||||
|
||||
private:
|
||||
std::string backendUrl_;
|
||||
std::string lastError_;
|
||||
|
||||
/**
|
||||
* @brief Perform HTTP POST request
|
||||
* @param endpoint API endpoint (e.g., "/api/merge")
|
||||
* @param jsonBody JSON request body
|
||||
* @param response Output response string
|
||||
* @return true if successful, false on error
|
||||
*/
|
||||
bool post(const std::string& endpoint, const std::string& jsonBody, std::string& response);
|
||||
};
|
||||
|
||||
#endif // HTTP_CLIENT_H
|
||||
47
frontends/cli/src/file_utils.cpp
Normal file
47
frontends/cli/src/file_utils.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "file_utils.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
|
||||
bool FileUtils::readLines(const std::string& filePath, std::vector<std::string>& lines) {
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lines.clear();
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
lines.push_back(line);
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileUtils::writeLines(const std::string& filePath, const std::vector<std::string>& lines) {
|
||||
std::ofstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& line : lines) {
|
||||
file << line << "\n";
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileUtils::fileExists(const std::string& filePath) {
|
||||
struct stat buffer;
|
||||
return (stat(filePath.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
long FileUtils::getFileSize(const std::string& filePath) {
|
||||
struct stat buffer;
|
||||
if (stat(filePath.c_str(), &buffer) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return buffer.st_size;
|
||||
}
|
||||
136
frontends/cli/src/http_client.cpp
Normal file
136
frontends/cli/src/http_client.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "http_client.h"
|
||||
#include <curl/curl.h>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
// Callback for libcurl to write response data
|
||||
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||
((std::string*)userp)->append((char*)contents, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
HttpClient::HttpClient(const std::string& backendUrl)
|
||||
: backendUrl_(backendUrl), lastError_("") {
|
||||
}
|
||||
|
||||
bool HttpClient::post(const std::string& endpoint, const std::string& jsonBody, std::string& response) {
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
lastError_ = "Failed to initialize CURL";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = backendUrl_ + endpoint;
|
||||
response.clear();
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonBody.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
|
||||
struct curl_slist* headers = nullptr;
|
||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
bool success = (res == CURLE_OK);
|
||||
if (!success) {
|
||||
lastError_ = std::string("CURL error: ") + curl_easy_strerror(res);
|
||||
}
|
||||
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool HttpClient::performMerge(
|
||||
const std::vector<std::string>& base,
|
||||
const std::vector<std::string>& ours,
|
||||
const std::vector<std::string>& theirs,
|
||||
std::vector<std::string>& merged,
|
||||
bool& hasConflicts
|
||||
) {
|
||||
// Build JSON request
|
||||
std::ostringstream json;
|
||||
json << "{";
|
||||
json << "\"base\":[";
|
||||
for (size_t i = 0; i < base.size(); ++i) {
|
||||
json << "\"" << base[i] << "\"";
|
||||
if (i < base.size() - 1) json << ",";
|
||||
}
|
||||
json << "],";
|
||||
json << "\"ours\":[";
|
||||
for (size_t i = 0; i < ours.size(); ++i) {
|
||||
json << "\"" << ours[i] << "\"";
|
||||
if (i < ours.size() - 1) json << ",";
|
||||
}
|
||||
json << "],";
|
||||
json << "\"theirs\":[";
|
||||
for (size_t i = 0; i < theirs.size(); ++i) {
|
||||
json << "\"" << theirs[i] << "\"";
|
||||
if (i < theirs.size() - 1) json << ",";
|
||||
}
|
||||
json << "]";
|
||||
json << "}";
|
||||
|
||||
std::string response;
|
||||
if (!post("/api/merge", json.str(), response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse JSON response (simple parsing for now)
|
||||
// In a production system, use a proper JSON library like nlohmann/json
|
||||
merged.clear();
|
||||
hasConflicts = (response.find("\"has_conflicts\":true") != std::string::npos);
|
||||
|
||||
// Extract merged lines from response
|
||||
// This is a simplified parser - production code should use a JSON library
|
||||
size_t mergedPos = response.find("\"merged\":");
|
||||
if (mergedPos != std::string::npos) {
|
||||
size_t startBracket = response.find("[", mergedPos);
|
||||
size_t endBracket = response.find("]", startBracket);
|
||||
if (startBracket != std::string::npos && endBracket != std::string::npos) {
|
||||
std::string mergedArray = response.substr(startBracket + 1, endBracket - startBracket - 1);
|
||||
|
||||
// Parse lines (simplified)
|
||||
size_t pos = 0;
|
||||
while (pos < mergedArray.size()) {
|
||||
size_t quoteStart = mergedArray.find("\"", pos);
|
||||
if (quoteStart == std::string::npos) break;
|
||||
size_t quoteEnd = mergedArray.find("\"", quoteStart + 1);
|
||||
if (quoteEnd == std::string::npos) break;
|
||||
|
||||
std::string line = mergedArray.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
|
||||
merged.push_back(line);
|
||||
pos = quoteEnd + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpClient::checkBackend() {
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
lastError_ = "Failed to initialize CURL";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = backendUrl_ + "/";
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
bool success = (res == CURLE_OK);
|
||||
|
||||
if (!success) {
|
||||
lastError_ = std::string("Cannot reach backend: ") + curl_easy_strerror(res);
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
return success;
|
||||
}
|
||||
243
frontends/cli/src/main.cpp
Normal file
243
frontends/cli/src/main.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "http_client.h"
|
||||
#include "file_utils.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
/**
|
||||
* @brief Print usage information
|
||||
*/
|
||||
void printUsage(const char* programName) {
|
||||
std::cout << "WizardMerge CLI Frontend - Intelligent Merge Conflict Resolution\n\n";
|
||||
std::cout << "Usage:\n";
|
||||
std::cout << " " << programName << " [OPTIONS] merge --base <file> --ours <file> --theirs <file>\n";
|
||||
std::cout << " " << programName << " [OPTIONS] git-resolve [FILE]\n";
|
||||
std::cout << " " << programName << " --help\n";
|
||||
std::cout << " " << programName << " --version\n\n";
|
||||
std::cout << "Global Options:\n";
|
||||
std::cout << " --backend <url> Backend server URL (default: http://localhost:8080)\n";
|
||||
std::cout << " -v, --verbose Enable verbose output\n";
|
||||
std::cout << " -q, --quiet Suppress non-error output\n";
|
||||
std::cout << " -h, --help Show this help message\n";
|
||||
std::cout << " --version Show version information\n\n";
|
||||
std::cout << "Commands:\n";
|
||||
std::cout << " merge Perform three-way merge\n";
|
||||
std::cout << " --base <file> Base version file (required)\n";
|
||||
std::cout << " --ours <file> Our version file (required)\n";
|
||||
std::cout << " --theirs <file> Their version file (required)\n";
|
||||
std::cout << " -o, --output <file> Output file (default: stdout)\n";
|
||||
std::cout << " --format <format> Output format: text, json (default: text)\n\n";
|
||||
std::cout << " git-resolve Resolve Git merge conflicts (not yet implemented)\n";
|
||||
std::cout << " [FILE] Specific file to resolve (optional)\n\n";
|
||||
std::cout << "Examples:\n";
|
||||
std::cout << " " << programName << " merge --base base.txt --ours ours.txt --theirs theirs.txt\n";
|
||||
std::cout << " " << programName << " merge --base base.txt --ours ours.txt --theirs theirs.txt -o result.txt\n";
|
||||
std::cout << " " << programName << " --backend http://remote:8080 merge --base b.txt --ours o.txt --theirs t.txt\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print version information
|
||||
*/
|
||||
void printVersion() {
|
||||
std::cout << "WizardMerge CLI Frontend v1.0.0\n";
|
||||
std::cout << "Part of the WizardMerge Intelligent Merge Conflict Resolution system\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse command-line arguments and execute merge
|
||||
*/
|
||||
int main(int argc, char* argv[]) {
|
||||
// Default configuration
|
||||
std::string backendUrl = "http://localhost:8080";
|
||||
bool verbose = false;
|
||||
bool quiet = false;
|
||||
std::string command;
|
||||
std::string baseFile, oursFile, theirsFile, outputFile;
|
||||
std::string format = "text";
|
||||
|
||||
// Check environment variable
|
||||
const char* envBackend = std::getenv("WIZARDMERGE_BACKEND");
|
||||
if (envBackend) {
|
||||
backendUrl = envBackend;
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
printUsage(argv[0]);
|
||||
return 0;
|
||||
} else if (arg == "--version") {
|
||||
printVersion();
|
||||
return 0;
|
||||
} else if (arg == "--backend") {
|
||||
if (i + 1 < argc) {
|
||||
backendUrl = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: --backend requires an argument\n";
|
||||
return 2;
|
||||
}
|
||||
} else if (arg == "--verbose" || arg == "-v") {
|
||||
verbose = true;
|
||||
} else if (arg == "--quiet" || arg == "-q") {
|
||||
quiet = true;
|
||||
} else if (arg == "merge") {
|
||||
command = "merge";
|
||||
} else if (arg == "git-resolve") {
|
||||
command = "git-resolve";
|
||||
} else if (arg == "--base") {
|
||||
if (i + 1 < argc) {
|
||||
baseFile = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: --base requires an argument\n";
|
||||
return 2;
|
||||
}
|
||||
} else if (arg == "--ours") {
|
||||
if (i + 1 < argc) {
|
||||
oursFile = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: --ours requires an argument\n";
|
||||
return 2;
|
||||
}
|
||||
} else if (arg == "--theirs") {
|
||||
if (i + 1 < argc) {
|
||||
theirsFile = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: --theirs requires an argument\n";
|
||||
return 2;
|
||||
}
|
||||
} else if (arg == "--output" || arg == "-o") {
|
||||
if (i + 1 < argc) {
|
||||
outputFile = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: --output requires an argument\n";
|
||||
return 2;
|
||||
}
|
||||
} else if (arg == "--format") {
|
||||
if (i + 1 < argc) {
|
||||
format = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: --format requires an argument\n";
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if command was provided
|
||||
if (command.empty()) {
|
||||
std::cerr << "Error: No command specified\n\n";
|
||||
printUsage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Execute command
|
||||
if (command == "merge") {
|
||||
// Validate required arguments
|
||||
if (baseFile.empty() || oursFile.empty() || theirsFile.empty()) {
|
||||
std::cerr << "Error: merge command requires --base, --ours, and --theirs arguments\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Check files exist
|
||||
if (!FileUtils::fileExists(baseFile)) {
|
||||
std::cerr << "Error: Base file not found: " << baseFile << "\n";
|
||||
return 4;
|
||||
}
|
||||
if (!FileUtils::fileExists(oursFile)) {
|
||||
std::cerr << "Error: Ours file not found: " << oursFile << "\n";
|
||||
return 4;
|
||||
}
|
||||
if (!FileUtils::fileExists(theirsFile)) {
|
||||
std::cerr << "Error: Theirs file not found: " << theirsFile << "\n";
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Backend URL: " << backendUrl << "\n";
|
||||
std::cout << "Base file: " << baseFile << "\n";
|
||||
std::cout << "Ours file: " << oursFile << "\n";
|
||||
std::cout << "Theirs file: " << theirsFile << "\n";
|
||||
}
|
||||
|
||||
// Read input files
|
||||
std::vector<std::string> baseLines, oursLines, theirsLines;
|
||||
if (!FileUtils::readLines(baseFile, baseLines)) {
|
||||
std::cerr << "Error: Failed to read base file\n";
|
||||
return 4;
|
||||
}
|
||||
if (!FileUtils::readLines(oursFile, oursLines)) {
|
||||
std::cerr << "Error: Failed to read ours file\n";
|
||||
return 4;
|
||||
}
|
||||
if (!FileUtils::readLines(theirsFile, theirsLines)) {
|
||||
std::cerr << "Error: Failed to read theirs file\n";
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Read " << baseLines.size() << " lines from base\n";
|
||||
std::cout << "Read " << oursLines.size() << " lines from ours\n";
|
||||
std::cout << "Read " << theirsLines.size() << " lines from theirs\n";
|
||||
}
|
||||
|
||||
// Connect to backend and perform merge
|
||||
HttpClient client(backendUrl);
|
||||
|
||||
if (!quiet) {
|
||||
std::cout << "Connecting to backend: " << backendUrl << "\n";
|
||||
}
|
||||
|
||||
if (!client.checkBackend()) {
|
||||
std::cerr << "Error: Cannot connect to backend: " << client.getLastError() << "\n";
|
||||
std::cerr << "Make sure the backend server is running on " << backendUrl << "\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
std::cout << "Performing three-way merge...\n";
|
||||
}
|
||||
|
||||
std::vector<std::string> mergedLines;
|
||||
bool hasConflicts = false;
|
||||
|
||||
if (!client.performMerge(baseLines, oursLines, theirsLines, mergedLines, hasConflicts)) {
|
||||
std::cerr << "Error: Merge failed: " << client.getLastError() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Output results
|
||||
if (!quiet) {
|
||||
std::cout << "Merge completed. Has conflicts: " << (hasConflicts ? "Yes" : "No") << "\n";
|
||||
std::cout << "Result has " << mergedLines.size() << " lines\n";
|
||||
}
|
||||
|
||||
// Write output
|
||||
if (outputFile.empty()) {
|
||||
// Write to stdout
|
||||
for (const auto& line : mergedLines) {
|
||||
std::cout << line << "\n";
|
||||
}
|
||||
} else {
|
||||
if (!FileUtils::writeLines(outputFile, mergedLines)) {
|
||||
std::cerr << "Error: Failed to write output file\n";
|
||||
return 4;
|
||||
}
|
||||
if (!quiet) {
|
||||
std::cout << "Output written to: " << outputFile << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return hasConflicts ? 5 : 0;
|
||||
|
||||
} else if (command == "git-resolve") {
|
||||
std::cerr << "Error: git-resolve command not yet implemented\n";
|
||||
return 1;
|
||||
} else {
|
||||
std::cerr << "Error: Unknown command: " << command << "\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
66
frontends/nextjs/README.md
Normal file
66
frontends/nextjs/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# WizardMerge Frontend
|
||||
|
||||
Next.js-based web frontend for WizardMerge.
|
||||
|
||||
## Runtime
|
||||
|
||||
- **Package Manager**: bun
|
||||
- **Framework**: Next.js 14
|
||||
- **Language**: TypeScript
|
||||
- **Styling**: Plain CSS (Tailwind CSS planned for future)
|
||||
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```sh
|
||||
# Install bun
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```sh
|
||||
# Install dependencies
|
||||
bun install
|
||||
|
||||
# Run development server
|
||||
bun run dev
|
||||
|
||||
# Build for production
|
||||
bun run build
|
||||
|
||||
# Start production server
|
||||
bun run start
|
||||
```
|
||||
|
||||
The application will be available at http://localhost:3000
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── app/ # Next.js app directory
|
||||
│ ├── layout.tsx # Root layout
|
||||
│ ├── page.tsx # Home page
|
||||
│ └── globals.css # Global styles
|
||||
├── public/ # Static assets
|
||||
├── package.json # Dependencies
|
||||
├── tsconfig.json # TypeScript config
|
||||
└── next.config.js # Next.js config
|
||||
```
|
||||
|
||||
## Features (Planned)
|
||||
|
||||
- Three-panel diff viewer
|
||||
- Conflict resolution interface
|
||||
- Real-time collaboration
|
||||
- Syntax highlighting
|
||||
- Integration with C++ backend via REST API
|
||||
|
||||
## Scripts
|
||||
|
||||
- `bun run dev` - Start development server
|
||||
- `bun run build` - Build for production
|
||||
- `bun run start` - Start production server
|
||||
- `bun run lint` - Run ESLint
|
||||
165
frontends/nextjs/app/globals.css
Normal file
165
frontends/nextjs/app/globals.css
Normal file
@@ -0,0 +1,165 @@
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 255, 255, 255;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.text-gray-600 {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.text-blue-600 {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mb-8 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.mt-8 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.gap-6 {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.bg-blue-50 {
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
ul.list-disc {
|
||||
list-style-type: disc;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
ul.list-inside {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.space-y-2 > * + * {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.md\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.max-w-6xl {
|
||||
max-width: 72rem;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.text-4xl {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
19
frontends/nextjs/app/layout.tsx
Normal file
19
frontends/nextjs/app/layout.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Metadata } from 'next'
|
||||
import './globals.css'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'WizardMerge - Intelligent Merge Conflict Resolution',
|
||||
description: 'Resolve merge conflicts with intelligent dependency-aware algorithms',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
73
frontends/nextjs/app/page.tsx
Normal file
73
frontends/nextjs/app/page.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="min-h-screen p-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-4">WizardMerge</h1>
|
||||
<p className="text-xl mb-8 text-gray-600">
|
||||
Intelligent Merge Conflict Resolution
|
||||
</p>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="border rounded-lg p-6">
|
||||
<h2 className="text-2xl font-semibold mb-3">Three-Way Merge</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Advanced merge algorithm with dependency analysis at text and LLVM-IR levels
|
||||
</p>
|
||||
<ul className="list-disc list-inside space-y-2 text-sm">
|
||||
<li>28.85% reduction in conflict resolution time</li>
|
||||
<li>Merge suggestions for 70%+ of conflict blocks</li>
|
||||
<li>Smart auto-resolution patterns</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg p-6">
|
||||
<h2 className="text-2xl font-semibold mb-3">Visual Interface</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Clean, intuitive UI for reviewing and resolving conflicts
|
||||
</p>
|
||||
<ul className="list-disc list-inside space-y-2 text-sm">
|
||||
<li>Three-panel diff view</li>
|
||||
<li>Syntax highlighting</li>
|
||||
<li>Keyboard shortcuts</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg p-6">
|
||||
<h2 className="text-2xl font-semibold mb-3">Git Integration</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Seamless integration with Git workflows
|
||||
</p>
|
||||
<ul className="list-disc list-inside space-y-2 text-sm">
|
||||
<li>Detect and list conflicted files</li>
|
||||
<li>Mark files as resolved</li>
|
||||
<li>Command-line interface</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg p-6">
|
||||
<h2 className="text-2xl font-semibold mb-3">Smart Analysis</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Context-aware code understanding
|
||||
</p>
|
||||
<ul className="list-disc list-inside space-y-2 text-sm">
|
||||
<li>Semantic merge for JSON, YAML, XML</li>
|
||||
<li>Language-aware merging (AST-based)</li>
|
||||
<li>Auto-resolution suggestions</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 p-6 bg-blue-50 rounded-lg">
|
||||
<h3 className="text-xl font-semibold mb-2">Getting Started</h3>
|
||||
<p className="text-gray-700">
|
||||
WizardMerge is currently in active development. See the{' '}
|
||||
<a href="https://github.com/johndoe6345789/WizardMerge" className="text-blue-600 underline">
|
||||
GitHub repository
|
||||
</a>{' '}
|
||||
for roadmap and progress.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
6
frontends/nextjs/next.config.js
Normal file
6
frontends/nextjs/next.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
22
frontends/nextjs/package.json
Normal file
22
frontends/nextjs/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "wizardmerge-frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^16.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
27
frontends/nextjs/tsconfig.json
Normal file
27
frontends/nextjs/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
78
frontends/qt6/CMakeLists.txt
Normal file
78
frontends/qt6/CMakeLists.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(wizardmerge-qt6 VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Qt6 configuration
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
# Find Qt6 packages
|
||||
find_package(Qt6 COMPONENTS Core Widgets Quick Network QUIET)
|
||||
|
||||
if(NOT Qt6_FOUND)
|
||||
message(WARNING "Qt6 not found. Skipping Qt6 frontend build.")
|
||||
message(WARNING "Install Qt6 to build the Qt6 frontend:")
|
||||
message(WARNING " - Ubuntu/Debian: sudo apt-get install qt6-base-dev qt6-declarative-dev")
|
||||
message(WARNING " - macOS: brew install qt@6")
|
||||
message(WARNING " - Windows: Download from https://www.qt.io/download")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Source files
|
||||
set(SOURCES
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
# QML files
|
||||
set(QML_FILES
|
||||
qml/main.qml
|
||||
)
|
||||
|
||||
# Create executable
|
||||
qt_add_executable(wizardmerge-qt6
|
||||
${SOURCES}
|
||||
)
|
||||
|
||||
# Add QML module
|
||||
qt_add_qml_module(wizardmerge-qt6
|
||||
URI WizardMerge
|
||||
VERSION 1.0
|
||||
QML_FILES ${QML_FILES}
|
||||
)
|
||||
|
||||
# Link Qt libraries
|
||||
target_link_libraries(wizardmerge-qt6 PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Widgets
|
||||
Qt6::Quick
|
||||
Qt6::Network
|
||||
)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(wizardmerge-qt6 PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
# Install target
|
||||
install(TARGETS wizardmerge-qt6
|
||||
BUNDLE DESTINATION .
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
|
||||
# Platform-specific settings
|
||||
if(WIN32)
|
||||
set_target_properties(wizardmerge-qt6 PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(wizardmerge-qt6 PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
endif()
|
||||
|
||||
message(STATUS "Qt6 frontend configured successfully")
|
||||
104
frontends/qt6/README.md
Normal file
104
frontends/qt6/README.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# WizardMerge Qt6 Frontend
|
||||
|
||||
Native desktop frontend for WizardMerge built with Qt6 and C++.
|
||||
|
||||
## Features
|
||||
|
||||
- Native desktop application for Linux, Windows, and macOS
|
||||
- Qt6 Widgets/QML-based UI
|
||||
- Direct integration with C++ backend
|
||||
- Offline capability
|
||||
- High performance
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Qt6 (6.2+)
|
||||
- CMake 3.16+
|
||||
- C++17 compiler (GCC 7+, Clang 6+, MSVC 2017+)
|
||||
- Ninja (recommended)
|
||||
|
||||
## Building
|
||||
|
||||
### Install Qt6
|
||||
|
||||
**Ubuntu/Debian:**
|
||||
```bash
|
||||
sudo apt-get install qt6-base-dev qt6-declarative-dev
|
||||
```
|
||||
|
||||
**macOS (Homebrew):**
|
||||
```bash
|
||||
brew install qt@6
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
Download and install Qt6 from https://www.qt.io/download
|
||||
|
||||
### Build the Application
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
./wizardmerge-qt6
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
qt6/
|
||||
├── CMakeLists.txt # CMake build configuration
|
||||
├── README.md # This file
|
||||
├── src/ # C++ source files
|
||||
│ └── main.cpp # Application entry point
|
||||
├── qml/ # QML UI files
|
||||
│ └── main.qml # Main window UI
|
||||
└── include/ # Header files
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Architecture
|
||||
|
||||
The Qt6 frontend communicates with the WizardMerge C++ backend via:
|
||||
- Direct library linking (for standalone mode)
|
||||
- HTTP API calls (for client-server mode)
|
||||
|
||||
### UI Components
|
||||
|
||||
The UI is built using QML for declarative UI design:
|
||||
- Three-panel diff viewer
|
||||
- Conflict resolution controls
|
||||
- Syntax highlighting
|
||||
- File navigation
|
||||
|
||||
## Configuration
|
||||
|
||||
The application can be configured via command-line arguments:
|
||||
|
||||
```bash
|
||||
# Open a specific file
|
||||
./wizardmerge-qt6 /path/to/conflicted/file
|
||||
|
||||
# Connect to remote backend
|
||||
./wizardmerge-qt6 --backend-url http://localhost:8080
|
||||
|
||||
# Use standalone mode (embedded backend)
|
||||
./wizardmerge-qt6 --standalone
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Qt6 Core
|
||||
- Qt6 Widgets
|
||||
- Qt6 Quick (QML)
|
||||
- Qt6 Network (for HTTP client)
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](../../LICENSE) for details.
|
||||
227
frontends/qt6/qml/main.qml
Normal file
227
frontends/qt6/qml/main.qml
Normal file
@@ -0,0 +1,227 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
visible: true
|
||||
width: 1200
|
||||
height: 800
|
||||
title: "WizardMerge - Intelligent Merge Conflict Resolution"
|
||||
|
||||
// Properties exposed from C++
|
||||
property string backendUrl: ""
|
||||
property bool standalone: false
|
||||
property string initialFile: ""
|
||||
|
||||
header: ToolBar {
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
text: "WizardMerge"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Label {
|
||||
text: standalone ? "Standalone Mode" : "Client Mode"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
text: "Open File"
|
||||
onClicked: fileDialog.open()
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
text: "Settings"
|
||||
onClicked: settingsDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main content area
|
||||
SplitView {
|
||||
anchors.fill: parent
|
||||
orientation: Qt.Horizontal
|
||||
|
||||
// Left panel - Base version
|
||||
Rectangle {
|
||||
SplitView.preferredWidth: parent.width / 3
|
||||
color: "#f5f5f5"
|
||||
border.color: "#cccccc"
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
text: "Base Version"
|
||||
font.bold: true
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
TextArea {
|
||||
id: baseText
|
||||
readOnly: true
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
wrapMode: TextEdit.NoWrap
|
||||
placeholderText: "Base version will appear here..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Middle panel - Ours version
|
||||
Rectangle {
|
||||
SplitView.preferredWidth: parent.width / 3
|
||||
color: "#e8f5e9"
|
||||
border.color: "#4caf50"
|
||||
border.width: 2
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
text: "Ours (Current Branch)"
|
||||
font.bold: true
|
||||
font.pixelSize: 14
|
||||
color: "#2e7d32"
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
TextArea {
|
||||
id: oursText
|
||||
readOnly: true
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
wrapMode: TextEdit.NoWrap
|
||||
placeholderText: "Our version will appear here..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Right panel - Theirs version
|
||||
Rectangle {
|
||||
SplitView.preferredWidth: parent.width / 3
|
||||
color: "#e3f2fd"
|
||||
border.color: "#2196f3"
|
||||
border.width: 2
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
text: "Theirs (Incoming Branch)"
|
||||
font.bold: true
|
||||
font.pixelSize: 14
|
||||
color: "#1565c0"
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
TextArea {
|
||||
id: theirsText
|
||||
readOnly: true
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
wrapMode: TextEdit.NoWrap
|
||||
placeholderText: "Their version will appear here..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Status bar
|
||||
footer: ToolBar {
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
text: "Backend: " + backendUrl
|
||||
font.pixelSize: 10
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Label {
|
||||
text: initialFile !== "" ? "File: " + initialFile : "No file loaded"
|
||||
font.pixelSize: 10
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Ready"
|
||||
font.pixelSize: 10
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// File dialog (placeholder)
|
||||
Dialog {
|
||||
id: fileDialog
|
||||
title: "Open File"
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
Label {
|
||||
text: "File selection not yet implemented.\nUse command line: wizardmerge-qt6 <file>"
|
||||
}
|
||||
}
|
||||
|
||||
// Settings dialog (placeholder)
|
||||
Dialog {
|
||||
id: settingsDialog
|
||||
title: "Settings"
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
text: "Backend URL:"
|
||||
}
|
||||
|
||||
TextField {
|
||||
Layout.fillWidth: true
|
||||
text: backendUrl
|
||||
placeholderText: "http://localhost:8080"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Standalone Mode"
|
||||
checked: standalone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Component initialization
|
||||
Component.onCompleted: {
|
||||
console.log("WizardMerge Qt6 UI initialized")
|
||||
console.log("Backend URL:", backendUrl)
|
||||
console.log("Standalone:", standalone)
|
||||
console.log("Initial File:", initialFile)
|
||||
}
|
||||
}
|
||||
88
frontends/qt6/src/main.cpp
Normal file
88
frontends/qt6/src/main.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QCommandLineParser>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QUrl>
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* @brief Main entry point for WizardMerge Qt6 frontend
|
||||
*
|
||||
* This application provides a native desktop interface for WizardMerge,
|
||||
* supporting both standalone mode (with embedded backend) and client mode
|
||||
* (connecting to a remote backend server).
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
app.setApplicationName("WizardMerge");
|
||||
app.setApplicationVersion("1.0.0");
|
||||
app.setOrganizationName("WizardMerge");
|
||||
app.setOrganizationDomain("wizardmerge.dev");
|
||||
|
||||
// Command line parser
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("WizardMerge - Intelligent Merge Conflict Resolution");
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
QCommandLineOption backendUrlOption(
|
||||
QStringList() << "b" << "backend-url",
|
||||
"Backend server URL (default: http://localhost:8080)",
|
||||
"url",
|
||||
"http://localhost:8080"
|
||||
);
|
||||
parser.addOption(backendUrlOption);
|
||||
|
||||
QCommandLineOption standaloneOption(
|
||||
QStringList() << "s" << "standalone",
|
||||
"Run in standalone mode with embedded backend"
|
||||
);
|
||||
parser.addOption(standaloneOption);
|
||||
|
||||
parser.addPositionalArgument("file", "File to open (optional)");
|
||||
|
||||
parser.process(app);
|
||||
|
||||
// Get command line arguments
|
||||
QString backendUrl = parser.value(backendUrlOption);
|
||||
bool standalone = parser.isSet(standaloneOption);
|
||||
QStringList positionalArgs = parser.positionalArguments();
|
||||
QString filePath = positionalArgs.isEmpty() ? QString() : positionalArgs.first();
|
||||
|
||||
// Create QML engine
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
// Expose application settings to QML
|
||||
QQmlContext* rootContext = engine.rootContext();
|
||||
rootContext->setContextProperty("backendUrl", backendUrl);
|
||||
rootContext->setContextProperty("standalone", standalone);
|
||||
rootContext->setContextProperty("initialFile", filePath);
|
||||
|
||||
// Load main QML file
|
||||
const QUrl url(u"qrc:/qt/qml/WizardMerge/main.qml"_qs);
|
||||
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
|
||||
&app, []() {
|
||||
std::cerr << "Error: Failed to load QML" << std::endl;
|
||||
QCoreApplication::exit(-1);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
engine.load(url);
|
||||
|
||||
if (engine.rootObjects().isEmpty()) {
|
||||
std::cerr << "Error: No root objects loaded from QML" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << "WizardMerge Qt6 Frontend Started" << std::endl;
|
||||
std::cout << "Backend URL: " << backendUrl.toStdString() << std::endl;
|
||||
std::cout << "Standalone Mode: " << (standalone ? "Yes" : "No") << std::endl;
|
||||
if (!filePath.isEmpty()) {
|
||||
std::cout << "Opening file: " << filePath.toStdString() << std::endl;
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
Reference in New Issue
Block a user