Sprint 1 Tier 1: Add Qt infrastructure and sci-shim module
ober
61c6ab7e813e7351bb563c24f2be1b304cdcd607
--- a/Makefile +++ b/Makefile @@ -2,9 +2,9 @@ SCHEME = scheme JERBOA = $(HOME)/mine/jerboa JSH = $(HOME)/mine/jerboa-shell/src GHERKIN = $(HOME)/mine/gherkin/src -LIBDIRS = --libdirs lib:$(JERBOA)/lib:$(JSH):$(GHERKIN):$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla/src +LIBDIRS = --libdirs lib:$(JERBOA)/lib:$(JSH):$(GHERKIN):$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla/src:$(HOME)/mine/chez-qt JERBUILD = $(SCHEME) --libdirs $(JERBOA)/lib --script $(JERBOA)/jerbuild.ss -export LD_LIBRARY_PATH := $(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla:$(HOME)/mine/jerboa-shell:$(LD_LIBRARY_PATH) +export LD_LIBRARY_PATH := $(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla:$(HOME)/mine/chez-qt:$(HOME)/mine/jerboa-shell:$(LD_LIBRARY_PATH) export CHEZ_SCINTILLA_LIB := $(HOME)/mine/chez-scintilla .PHONY: all build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated \ new file mode 100644 --- /dev/null +++ b/lib/jerboa-emacs/qt/sci-shim.sls @@ -0,0 +1,296 @@ +#!chezscheme +;;; Generated by jerbuild — DO NOT EDIT +;;; Source: src/jerboa-emacs/qt/sci-shim.ss + +(library (jerboa-emacs qt sci-shim) + (export sci-send sci-send/string sci-recv-string rgb->sci + doc-editor-register! doc-buffer-register! SCI_REPLACESEL + qt-plain-text-edit-create qt-plain-text-edit-set-text! + qt-plain-text-edit-text qt-plain-text-edit-append! + qt-plain-text-edit-clear! qt-plain-text-edit-set-read-only! + qt-plain-text-edit-read-only? + qt-plain-text-edit-set-placeholder! + qt-plain-text-edit-line-count + qt-plain-text-edit-set-max-block-count! + qt-plain-text-edit-cursor-line + qt-plain-text-edit-cursor-column + qt-plain-text-edit-set-line-wrap! + qt-on-plain-text-edit-text-changed! + qt-plain-text-edit-cursor-position + qt-plain-text-edit-set-cursor-position! + qt-plain-text-edit-move-cursor! + qt-plain-text-edit-select-all! + qt-plain-text-edit-selected-text + qt-plain-text-edit-selection-start + qt-plain-text-edit-selection-end + qt-plain-text-edit-has-selection? + qt-plain-text-edit-set-selection! + qt-plain-text-edit-insert-text! + qt-plain-text-edit-remove-selected-text! + qt-plain-text-edit-undo! qt-plain-text-edit-redo! + qt-plain-text-edit-can-undo? qt-plain-text-edit-cut! + qt-plain-text-edit-copy! qt-plain-text-edit-paste! + qt-plain-text-edit-text-length qt-plain-text-edit-text-range + qt-plain-text-edit-line-from-position + qt-plain-text-edit-line-end-position + qt-plain-text-edit-find-text + qt-plain-text-edit-ensure-cursor-visible! + qt-plain-text-edit-center-cursor! + qt-plain-text-edit-set-document! qt-plain-text-edit-document + qt-plain-text-document-create qt-text-document-create + qt-text-document-destroy! qt-text-document-modified? + qt-text-document-set-modified! qt-line-number-area-create + qt-line-number-area-destroy! + qt-line-number-area-set-visible! + qt-line-number-area-set-bg-color! + qt-line-number-area-set-fg-color! + qt-syntax-highlighter-create qt-syntax-highlighter-destroy! + qt-syntax-highlighter-add-rule! + qt-syntax-highlighter-add-keywords! + qt-syntax-highlighter-add-multiline-rule! + qt-syntax-highlighter-rehighlight! + qt-syntax-highlighter-clear-rules! + qt-extra-selections-clear! qt-extra-selection-add-line! + qt-extra-selection-add-range! qt-extra-selections-apply!) + (import + (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;- + getenv path-extension path-absolute? thread? make-mutex + mutex? mutex-name) + (jerboa-emacs core) (chez-scintilla constants) + (chez-scintilla scintilla) (chez-qt qt) (std sugar) + (jerboa core) (jerboa runtime)) + (def SCI_REPLACESEL 2170) + (def (sci-send sci msg (wparam 0) (lparam 0)) + (qt-scintilla-send-message sci msg wparam lparam)) + (def (sci-send/string sci msg str (wparam 0)) + (qt-scintilla-send-message/string sci msg str wparam)) + (def (sci-recv-string sci msg (wparam 0)) + (qt-scintilla-receive-string sci msg wparam)) + (def (rgb->sci r g b) (+ r (* 256 g) (* 65536 b))) + (def *doc-editor-map* (make-hash-table)) + (def *doc-buffer-map* (make-hash-table)) + (def (doc-editor-register! doc editor) + (hash-put! *doc-editor-map* doc editor)) + (def (doc-buffer-register! doc buf) + (hash-put! *doc-buffer-map* doc buf)) + (def (qt-plain-text-edit-create parent: (parent #f)) + (qt-scintilla-create 'parent: parent)) + (def (qt-plain-text-edit-text sci) (qt-scintilla-text sci)) + (def (qt-plain-text-edit-set-text! sci text) + (verbose-log! + "qt-plain-text-edit-set-text! len=" + (number->string (string-length text))) + (qt-scintilla-set-text! sci text) + (verbose-log! "qt-plain-text-edit-set-text! done") + (let ([new-len (sci-send sci SCI_GETLENGTH)]) + (when (> (sci-send sci SCI_GETCURRENTPOS) new-len) + (sci-send sci SCI_GOTOPOS new-len)) + (when (> (sci-send sci SCI_GETANCHOR) new-len) + (sci-send sci SCI_SETANCHOR new-len)))) + (def (qt-plain-text-edit-append! sci text) + (let ([to-append (string-append "\n" text)]) + (sci-send/string + sci + SCI_APPENDTEXT + to-append + (string-length to-append)))) + (def (qt-plain-text-edit-clear! sci) + (sci-send sci SCI_CLEARALL)) + (def (qt-plain-text-edit-insert-text! sci text) + (sci-send/string sci SCI_REPLACESEL text)) + (def (qt-plain-text-edit-remove-selected-text! sci) + (sci-send/string sci SCI_REPLACESEL "")) + (def (qt-plain-text-edit-text-length sci) + (qt-scintilla-text-length sci)) + (def (qt-plain-text-edit-text-range sci start end) + (let ([text (qt-scintilla-text sci)]) + (if (and (>= start 0) + (<= end (string-length text)) + (<= start end)) + (substring text start end) + ""))) + (def (qt-plain-text-edit-cursor-position sci) + (sci-send sci SCI_GETCURRENTPOS)) + (def (qt-plain-text-edit-set-cursor-position! sci pos) + (sci-send + sci + SCI_GOTOPOS + (min pos (sci-send sci SCI_GETLENGTH)))) + (def (qt-plain-text-edit-cursor-line sci) + (sci-send + sci + SCI_LINEFROMPOSITION + (sci-send sci SCI_GETCURRENTPOS))) + (def (qt-plain-text-edit-cursor-column sci) + (sci-send + sci + SCI_GETCOLUMN + (sci-send sci SCI_GETCURRENTPOS))) + (def (qt-plain-text-edit-line-count sci) + (sci-send sci SCI_GETLINECOUNT)) + (def (qt-plain-text-edit-line-from-position sci pos) + (sci-send sci SCI_LINEFROMPOSITION pos)) + (def (qt-plain-text-edit-line-end-position sci line) + (sci-send sci SCI_GETLINEENDPOSITION line)) + (def (qt-plain-text-edit-move-cursor! + sci + op + mode: + (mode #f)) + (let* ([keep-anchor? (and mode (= mode QT_KEEP_ANCHOR))] + [cur-pos (sci-send sci SCI_GETCURRENTPOS)] + [new-pos cur-pos]) + (let ([safe-pos (min new-pos (sci-send sci SCI_GETLENGTH))]) + (if keep-anchor? + (sci-send sci SCI_SETCURRENTPOS safe-pos) + (sci-send sci SCI_GOTOPOS safe-pos))))) + (def (qt-plain-text-edit-ensure-cursor-visible! sci) + (sci-send sci SCI_SCROLLCARET)) + (def (qt-plain-text-edit-center-cursor! sci) + (sci-send sci SCI_SCROLLCARET)) + (def (qt-plain-text-edit-select-all! sci) + (sci-send sci SCI_SELECTALL)) + (def (qt-plain-text-edit-selected-text sci) + (sci-recv-string sci SCI_GETSELTEXT)) + (def (qt-plain-text-edit-selection-start sci) + (sci-send sci SCI_GETSELECTIONSTART)) + (def (qt-plain-text-edit-selection-end sci) + (sci-send sci SCI_GETSELECTIONEND)) + (def (qt-plain-text-edit-has-selection? sci) + (not (= (sci-send sci SCI_GETSELECTIONSTART) + (sci-send sci SCI_GETSELECTIONEND)))) + (def (qt-plain-text-edit-set-selection! sci start end) + (let ([doc-len (sci-send sci SCI_GETLENGTH)]) + (sci-send + sci + SCI_SETSEL + (min start doc-len) + (min end doc-len)))) + (def (qt-plain-text-edit-undo! sci) (sci-send sci SCI_UNDO)) + (def (qt-plain-text-edit-redo! sci) (sci-send sci SCI_REDO)) + (def (qt-plain-text-edit-can-undo? sci) + (not (= 0 (sci-send sci SCI_CANUNDO)))) + (def (qt-plain-text-edit-cut! sci) (sci-send sci SCI_CUT)) + (def (qt-plain-text-edit-copy! sci) (sci-send sci SCI_COPY)) + (def (qt-plain-text-edit-paste! sci) + (sci-send sci SCI_PASTE)) + (def (qt-plain-text-edit-set-read-only! sci ro?) + (sci-send sci SCI_SETREADONLY (if ro? 1 0))) + (def (qt-plain-text-edit-read-only? sci) + (not (zero? (sci-send sci SCI_GETREADONLY)))) + (def (qt-plain-text-edit-set-line-wrap! sci wrap?) + (sci-send + sci + SCI_SETWRAPMODE + (if wrap? SC_WRAP_WORD SC_WRAP_NONE))) + (def (qt-plain-text-edit-set-placeholder! sci text) (void)) + (def (qt-plain-text-edit-set-max-block-count! sci count) + (void)) + (def (qt-on-plain-text-edit-text-changed! sci handler) + (qt-on-scintilla-text-changed! sci handler)) + (def (qt-plain-text-edit-find-text sci text . flags) + (let* ([pos (sci-send sci SCI_GETCURRENTPOS)] + [len (sci-send sci SCI_GETTEXTLENGTH)]) + (sci-send sci SCI_SETTARGETSTART pos) + (sci-send sci SCI_SETTARGETEND len) + (sci-send sci SCI_SETSEARCHFLAGS 0) + (let ([found (sci-send/string + sci + SCI_SEARCHINTARGET + text + (string-length text))]) + (if (>= found 0) found -1)))) + (def (qt-plain-text-edit-set-document! sci doc) + (sci-send sci SCI_SETDOCPOINTER 0 doc)) + (def (qt-plain-text-edit-document sci) + (sci-send sci SCI_GETDOCPOINTER)) + (def (qt-plain-text-document-create) #f) + (def (qt-text-document-create) #f) + (def (qt-text-document-destroy! doc) (void)) + (def (qt-text-document-modified? doc) + (let ([buf (hash-get *doc-buffer-map* doc)]) + (if buf (buffer-modified buf) #f))) + (def (qt-text-document-set-modified! doc modified?) + (let ([buf (hash-get *doc-buffer-map* doc)]) + (when buf + (buffer-modified-set! buf modified?) + (unless modified? + (let ([ed (hash-get *doc-editor-map* doc)]) + (when ed (sci-send ed SCI_SETSAVEPOINT))))))) + (def (qt-line-number-area-create editor) #f) + (def (qt-line-number-area-destroy! lna) (void)) + (def (qt-line-number-area-set-visible! lna visible?) (void)) + (def (qt-line-number-area-set-bg-color! lna r g b) (void)) + (def (qt-line-number-area-set-fg-color! lna r g b) (void)) + (def (qt-syntax-highlighter-create doc) #f) + (def (qt-syntax-highlighter-destroy! h) (void)) + (def (qt-syntax-highlighter-add-rule! h pattern r g b bold? + italic?) + (void)) + (def (qt-syntax-highlighter-add-keywords! h keywords r g b + bold? italic?) + (void)) + (def (qt-syntax-highlighter-add-multiline-rule! h start end + r g b bold? italic?) + (void)) + (def (qt-syntax-highlighter-rehighlight! h) (void)) + (def (qt-syntax-highlighter-clear-rules! h) (void)) + (def *indic-current-line* 8) + (def *indic-brace-match* 9) + (def *indic-brace-bad* 10) + (def *indic-search* 11) + (def *pending-decorations* (list)) + (def (qt-extra-selections-clear! sci) + (set! *pending-decorations* (list)) + (let ([len (sci-send sci SCI_GETTEXTLENGTH)]) + (for-each + (lambda (indic) + (sci-send sci SCI_SETINDICATORCURRENT indic) + (sci-send sci SCI_INDICATORCLEARRANGE 0 len)) + (list + *indic-current-line* + *indic-brace-match* + *indic-brace-bad* + *indic-search*)))) + (def (qt-extra-selection-add-line! sci line bg-r bg-g bg-b) + (sci-send sci SCI_SETCARETLINEVISIBLE 1) + (sci-send + sci + SCI_SETCARETLINEBACK + (rgb->sci bg-r bg-g bg-b))) + (def (qt-extra-selection-add-range! sci pos len fg-r fg-g + fg-b bg-r bg-g bg-b bold: (bold? #f)) + (set! *pending-decorations* + (cons + (list pos len fg-r fg-g fg-b bg-r bg-g bg-b bold?) + *pending-decorations*))) + (def (qt-extra-selections-apply! sci) + (for-each + (lambda (dec) + (let ([pos (car dec)] + [len (cadr dec)] + [bg-r (list-ref dec 5)] + [bg-g (list-ref dec 6)] + [bg-b (list-ref dec 7)]) + (sci-send sci SCI_SETINDICATORCURRENT *indic-brace-match*) + (sci-send + sci + SCI_INDICSETSTYLE + *indic-brace-match* + INDIC_ROUNDBOX) + (sci-send + sci + SCI_INDICSETFORE + *indic-brace-match* + (rgb->sci bg-r bg-g bg-b)) + (let* ([doc-len (sci-send sci SCI_GETLENGTH)] + [safe-pos (min pos doc-len)] + [safe-len (min len (- doc-len safe-pos))]) + (when (> safe-len 0) + (sci-send + sci + SCI_INDICATORFILLRANGE + safe-pos + safe-len))))) + *pending-decorations*) + (set! *pending-decorations* (list)))) new file mode 100644 --- /dev/null +++ b/plan.md @@ -0,0 +1,352 @@ +# Jerboa-Emacs Feature Parity Plan + +**Goal:** Bring jerboa-emacs (Chez Scheme port) to full feature parity with gerbil-emacs (Gerbil Scheme original). + +**Date:** 2026-03-17 + +--- + +## Executive Summary + +| Category | gerbil-emacs | jerboa-emacs | Gap | +|----------|-------------|--------------|-----| +| Core modules | 65 files | 65 files | ✅ Complete | +| Qt backend | 46 files (~50k lines) | 0 files | ❌ Missing | +| Test files | 30 files | 27 files | ⚠️ Partial | +| Total lines | ~88k | ~65k | ~23k lines | + +The TUI (terminal) backend is complete. The primary gap is the **Qt graphical frontend** (~50k lines across 46 modules). + +--- + +## Phase 1: Qt Backend Infrastructure (Priority: High) + +### 1.1 Create Qt Directory Structure + +```bash +mkdir -p src/jerboa-emacs/qt +``` + +### 1.2 Port Core Qt Modules (in order) + +These modules must be ported first as others depend on them: + +| Module | Lines | Description | Dependencies | +|--------|-------|-------------|--------------| +| `qt/sci-shim.ss` | 536 | QScintilla compatibility layer | chez-qt, chez-scintilla | +| `qt/keymap.ss` | ~200 | Qt-specific key handling | sci-shim | +| `qt/buffer.ss` | 65 | Qt buffer wrapper | sci-shim | +| `qt/window.ss` | 567 | Qt window management | buffer, keymap | +| `qt/modeline.ss` | ~150 | Qt modeline widget | window | +| `qt/echo.ss` | ~200 | Qt echo area/minibuffer | window | +| `qt/highlight.ss` | ~300 | Qt syntax highlighting | sci-shim | + +**Porting Instructions:** + +1. Copy the Gerbil source file from `~/mine/gerbil-emacs/qt/` +2. Convert Gerbil syntax to Chez Scheme: + - `def` → `define` (or keep with `(std sugar)`) + - `defstruct` → `define-record-type` or use jerboa macros + - `:module/path` imports → `(module path)` library imports + - `begin-ffi` blocks → `foreign-procedure` with C shim .so files + - `displayln` → `(display ...) (newline)` + - `with-catch` → `guard` or `with-exception-handler` +3. Update library declaration: `(library (jerboa-emacs qt module-name) ...)` +4. Test compilation: `scheme --libdirs lib --script test-file.ss` + +### 1.3 Port Command Modules (~35 files) + +These implement editor commands for the Qt backend: + +``` +commands-core.ss, commands-core2.ss # Basic editing +commands-edit.ss, commands-edit2.ss # Text manipulation +commands-search.ss, commands-search2.ss # Search/replace +commands-file.ss, commands-file2.ss # File operations +commands-sexp.ss, commands-sexp2.ss # S-expression editing +commands-shell.ss, commands-shell2.ss # Shell integration +commands-vcs.ss, commands-vcs2.ss # Git/VCS integration +commands-ide.ss, commands-ide2.ss # IDE features +commands-modes.ss, commands-modes2.ss # Major/minor modes +commands-config.ss, commands-config2.ss # Configuration +commands-aliases.ss, commands-aliases2.ss +commands-parity.ss through commands-parity5.ss +commands-lsp.ss # LSP protocol +commands.ss # Facade module +``` + +### 1.4 Port Specialized Qt Modules + +| Module | Lines | Description | +|--------|-------|-------------| +| `qt/lsp-client.ss` | 595 | Language Server Protocol client | +| `qt/magit.ss` | ~800 | Git interface (magit-like) | +| `qt/image.ss` | ~300 | Image display support | +| `qt/helm-qt.ss` | ~400 | Helm framework for Qt | +| `qt/helm-commands.ss` | ~300 | Helm command definitions | +| `qt/snippets.ss` | ~200 | Code snippets | +| `qt/menubar.ss` | ~400 | Qt menubar integration | +| `qt/app.ss` | 1289 | Qt application lifecycle | +| `qt/main.ss` | ~100 | Qt executable entry point | + +--- + +## Phase 2: Dependencies (Priority: High) + +### 2.1 Required Chez Scheme Packages + +The Qt backend requires Chez Scheme ports of these Gerbil packages: + +| Gerbil Package | Chez Port Needed | Status | +|----------------|------------------|--------| +| `gerbil-qt` | `chez-qt` | Check if exists | +| `gerbil-scintilla` | `chez-scintilla` | ✅ Exists (referenced in Makefile) | +| `gerbil-litehtml` | `chez-litehtml` | Needed for HTML rendering | + +**Action:** Verify `chez-qt` exists at `~/mine/chez-qt` or port it. + +### 2.2 FFI Shim Libraries + +Create C shim libraries for Qt FFI (similar to `support/pty_shim.c`): + +```bash +# In support/ directory: +qt_shim.c # Qt widget bindings +sci_shim.c # QScintilla bindings (may exist in chez-scintilla) +litehtml_shim.c # litehtml bindings +``` + +--- + +## Phase 3: Build System Updates (Priority: Medium) + +### 3.1 Update Makefile + +Add Qt build targets: + +```makefile +# Add to Makefile +QT_MODULES = qt/sci-shim qt/keymap qt/buffer qt/window qt/modeline \ + qt/echo qt/highlight qt/app qt/main ... + +build-qt: build + $(JERBUILD) src/jerboa-emacs/qt/ lib/jerboa-emacs/qt/ + +run-qt: build-qt + $(SCHEME) $(LIBDIRS) --script qt-main.ss + +test-qt-functional: + $(SCHEME) $(LIBDIRS) --script tests/test-qt-functional.ss +``` + +### 3.2 Add Qt Library Dependencies + +Update `LIBDIRS` in Makefile: + +```makefile +CHEZ_QT = $(HOME)/mine/chez-qt/src +LIBDIRS = --libdirs lib:...:$(CHEZ_QT) +export LD_LIBRARY_PATH := ...:$(HOME)/mine/chez-qt:$(LD_LIBRARY_PATH) +``` + +### 3.3 Create manifest.ss + +```scheme +;; manifest.ss +(define version-manifest + '(("" . "0.1.0") + ("Jerboa" . "master") + ("Chez Scheme" . "10.x"))) +``` + +--- + +## Phase 4: Test Porting (Priority: Medium) + +### 4.1 Missing Test Files + +Port these test files from gerbil-emacs: + +| Test File | Status in jerboa-emacs | +|-----------|------------------------| +| `emacs-test.ss` | Missing - port to `tests/test-emacs.ss` | +| `functional-test.ss` | Missing - port to `tests/test-functional.ss` | +| `lsp-functional-test.ss` | Missing - needs Qt backend first | +| `lsp-protocol-test.ss` | Missing - needs LSP client first | +| `qt-functional-test.ss` | Missing - needs Qt backend first | +| `qt-highlight-test.ss` | Missing - needs Qt backend first | +| `term-hang-test.ss` | Missing - port for terminal testing | + +### 4.2 Test Naming Convention + +jerboa-emacs uses `test-*.ss` prefix; gerbil-emacs uses `*-test.ss` suffix. +Maintain jerboa-emacs convention when porting. + +--- + +## Phase 5: Documentation (Priority: Low) + +### 5.1 Update README.md + +Add: +- Qt backend installation instructions +- Dependencies list +- Build instructions for both TUI and Qt modes + +### 5.2 Create CLAUDE.md + +Add development guidance file (like gerbil-emacs has). + +--- + +## Implementation Order + +### Sprint 1: Foundation (Week 1-2) +1. [ ] Verify/create chez-qt package +2. [ ] Port `qt/sci-shim.ss` +3. [ ] Port `qt/keymap.ss` +4. [ ] Port `qt/buffer.ss` +5. [ ] Update Makefile with Qt targets + +### Sprint 2: Window System (Week 3-4) +1. [ ] Port `qt/window.ss` +2. [ ] Port `qt/modeline.ss` +3. [ ] Port `qt/echo.ss` +4. [ ] Port `qt/highlight.ss` +5. [ ] Basic Qt window test + +### Sprint 3: Commands Part 1 (Week 5-6) +1. [ ] Port `qt/commands-core.ss` and `qt/commands-core2.ss` +2. [ ] Port `qt/commands-edit.ss` and `qt/commands-edit2.ss` +3. [ ] Port `qt/commands-file.ss` and `qt/commands-file2.ss` +4. [ ] Port `qt/commands-search.ss` and `qt/commands-search2.ss` + +### Sprint 4: Commands Part 2 (Week 7-8) +1. [ ] Port remaining command modules +2. [ ] Port `qt/commands.ss` (facade) +3. [ ] Port `qt/helm-qt.ss` and `qt/helm-commands.ss` + +### Sprint 5: Advanced Features (Week 9-10) +1. [ ] Port `qt/lsp-client.ss` +2. [ ] Port `qt/commands-lsp.ss` +3. [ ] Port `qt/magit.ss` +4. [ ] Port `qt/image.ss` + +### Sprint 6: Application (Week 11-12) +1. [ ] Port `qt/menubar.ss` +2. [ ] Port `qt/app.ss` +3. [ ] Port `qt/main.ss` +4. [ ] Integration testing +5. [ ] Port Qt test files + +--- + +## Syntax Conversion Reference + +### Import Statements + +```scheme +;; Gerbil +(import :gemacs/core + :gerbil-qt/qt + (only-in :gemacs/buffer buffer-name)) + +;; Chez Scheme (jerboa-emacs) +(import (jerboa-emacs core) + (chez-qt qt) + (only (jerboa-emacs buffer) buffer-name)) +``` + +### Function Definitions + +```scheme +;; Gerbil +(def (my-function arg1 arg2) + (body ...)) + +;; Chez Scheme - keep def with (std sugar) +(def (my-function arg1 arg2) + (body ...)) +``` + +### Struct Definitions + +```scheme +;; Gerbil +(defstruct point (x y)) + +;; Chez Scheme - use jerboa macros or define-record-type +(defstruct point (x y)) ; if (std sugar) provides it +;; OR +(define-record-type point + (fields x y)) +``` + +### Error Handling + +```scheme +;; Gerbil +(with-catch + (lambda (e) (handle-error e)) + (lambda () (risky-operation))) + +;; Chez Scheme +(guard (e [else (handle-error e)]) + (risky-operation)) +;; OR with jerboa compatibility +(with-catch (lambda (e) (handle-error e)) + (lambda () (risky-operation))) +``` + +### FFI Bindings + +```scheme +;; Gerbil (begin-ffi block) +(begin-ffi (func-name) + (c-declare "...") + (define-c-lambda func-name (int string) int "c_func")) + +;; Chez Scheme (separate .c file + foreign-procedure) +;; In support/module_shim.c: +;; int c_func(int arg1, const char* arg2) { ... } +;; In .sls file: +(load-shared-object "support/module_shim.so") +(define func-name + (foreign-procedure "c_func" (int string) int)) +``` + +--- + +## Verification Checklist + +For each ported module: + +- [ ] File compiles without errors +- [ ] All exports are defined +- [ ] Imports resolve correctly +- [ ] FFI bindings work (if applicable) +- [ ] Unit tests pass (if tests exist) +- [ ] Integration with dependent modules works + +--- + +## Notes for Implementers + +1. **Start with sci-shim.ss** - It's the foundation for all Qt modules +2. **Test incrementally** - Compile and test each module before moving on +3. **Preserve API compatibility** - Function signatures should match gerbil-emacs +4. **Check chez-scintilla** - Many FFI bindings may already exist there +5. **Use existing patterns** - Look at `lib/jerboa-emacs/pty.sls` for FFI patterns +6. **Keep lib structure** - Generated .sls files go in `lib/jerboa-emacs/qt/` + +--- + +## File Counts Summary + +| Component | Files | Lines | Priority | +|-----------|-------|-------|----------| +| Qt core (sci-shim, window, buffer, etc.) | 10 | ~3,000 | P0 | +| Qt commands | 30 | ~35,000 | P1 | +| Qt advanced (LSP, magit, image) | 6 | ~12,000 | P2 | +| Tests | 8 | ~2,000 | P3 | +| **Total** | **54** | **~52,000** | | new file mode 100644 --- /dev/null +++ b/src/jerboa-emacs/qt/sci-shim.ss @@ -0,0 +1,292 @@ +;;; -*- Gerbil -*- +;;; QScintilla compatibility shim for jerboa-emacs +;;; +;;; Ported from gerbil-emacs/qt/sci-shim.ss +;;; Provides qt-plain-text-edit-* compatible functions backed by QScintilla. + +(export + ;; Local helpers + sci-send sci-send/string sci-recv-string rgb->sci + doc-editor-register! doc-buffer-register! + SCI_REPLACESEL + ;; Qt/Scintilla functions + qt-plain-text-edit-create qt-plain-text-edit-set-text! qt-plain-text-edit-text + qt-plain-text-edit-append! qt-plain-text-edit-clear! + qt-plain-text-edit-set-read-only! qt-plain-text-edit-read-only? + qt-plain-text-edit-set-placeholder! qt-plain-text-edit-line-count + qt-plain-text-edit-set-max-block-count! + qt-plain-text-edit-cursor-line qt-plain-text-edit-cursor-column + qt-plain-text-edit-set-line-wrap! qt-on-plain-text-edit-text-changed! + qt-plain-text-edit-cursor-position qt-plain-text-edit-set-cursor-position! + qt-plain-text-edit-move-cursor! qt-plain-text-edit-select-all! + qt-plain-text-edit-selected-text qt-plain-text-edit-selection-start + qt-plain-text-edit-selection-end qt-plain-text-edit-has-selection? + qt-plain-text-edit-set-selection! qt-plain-text-edit-insert-text! + qt-plain-text-edit-remove-selected-text! qt-plain-text-edit-undo! + qt-plain-text-edit-redo! qt-plain-text-edit-can-undo? + qt-plain-text-edit-cut! qt-plain-text-edit-copy! qt-plain-text-edit-paste! + qt-plain-text-edit-text-length qt-plain-text-edit-text-range + qt-plain-text-edit-line-from-position qt-plain-text-edit-line-end-position + qt-plain-text-edit-find-text qt-plain-text-edit-ensure-cursor-visible! + qt-plain-text-edit-center-cursor! + qt-plain-text-edit-set-document! qt-plain-text-edit-document + qt-plain-text-document-create qt-text-document-create qt-text-document-destroy! + qt-text-document-modified? qt-text-document-set-modified! + qt-line-number-area-create qt-line-number-area-destroy! + qt-line-number-area-set-visible! qt-line-number-area-set-bg-color! + qt-line-number-area-set-fg-color! + qt-syntax-highlighter-create qt-syntax-highlighter-destroy! + qt-syntax-highlighter-add-rule! qt-syntax-highlighter-add-keywords! + qt-syntax-highlighter-add-multiline-rule! qt-syntax-highlighter-rehighlight! + qt-syntax-highlighter-clear-rules! + qt-extra-selections-clear! qt-extra-selection-add-line! + qt-extra-selection-add-range! qt-extra-selections-apply!) + +(import + :jerboa-emacs/core + :chez-scintilla/constants + :chez-scintilla/scintilla + :chez-qt/qt + :std/sugar) + +;; SCI_REPLACESEL constant +(def SCI_REPLACESEL 2170) + +;; Helpers +(def (sci-send sci msg (wparam 0) (lparam 0)) + (qt-scintilla-send-message sci msg wparam lparam)) + +(def (sci-send/string sci msg str (wparam 0)) + (qt-scintilla-send-message/string sci msg str wparam)) + +(def (sci-recv-string sci msg (wparam 0)) + (qt-scintilla-receive-string sci msg wparam)) + +(def (rgb->sci r g b) + (+ r (* 256 g) (* 65536 b))) + +;; Document tracking +(def *doc-editor-map* (make-hash-table)) +(def *doc-buffer-map* (make-hash-table)) + +(def (doc-editor-register! doc editor) + (hash-put! *doc-editor-map* doc editor)) + +(def (doc-buffer-register! doc buf) + (hash-put! *doc-buffer-map* doc buf)) + +;; Widget creation +(def (qt-plain-text-edit-create parent: (parent #f)) + (qt-scintilla-create parent: parent)) + +;; Text operations +(def (qt-plain-text-edit-text sci) + (qt-scintilla-text sci)) + +(def (qt-plain-text-edit-set-text! sci text) + (verbose-log! "qt-plain-text-edit-set-text! len=" (number->string (string-length text))) + (qt-scintilla-set-text! sci text) + (verbose-log! "qt-plain-text-edit-set-text! done") + (let ((new-len (sci-send sci SCI_GETLENGTH))) + (when (> (sci-send sci SCI_GETCURRENTPOS) new-len) + (sci-send sci SCI_GOTOPOS new-len)) + (when (> (sci-send sci SCI_GETANCHOR) new-len) + (sci-send sci SCI_SETANCHOR new-len)))) + +(def (qt-plain-text-edit-append! sci text) + (let ((to-append (string-append "\n" text))) + (sci-send/string sci SCI_APPENDTEXT to-append (string-length to-append)))) + +(def (qt-plain-text-edit-clear! sci) + (sci-send sci SCI_CLEARALL)) + +(def (qt-plain-text-edit-insert-text! sci text) + (sci-send/string sci SCI_REPLACESEL text)) + +(def (qt-plain-text-edit-remove-selected-text! sci) + (sci-send/string sci SCI_REPLACESEL "")) + +(def (qt-plain-text-edit-text-length sci) + (qt-scintilla-text-length sci)) + +(def (qt-plain-text-edit-text-range sci start end) + (let ((text (qt-scintilla-text sci))) + (if (and (>= start 0) (<= end (string-length text)) (<= start end)) + (substring text start end) + ""))) + +;; Cursor operations +(def (qt-plain-text-edit-cursor-position sci) + (sci-send sci SCI_GETCURRENTPOS)) + +(def (qt-plain-text-edit-set-cursor-position! sci pos) + (sci-send sci SCI_GOTOPOS (min pos (sci-send sci SCI_GETLENGTH)))) + +(def (qt-plain-text-edit-cursor-line sci) + (sci-send sci SCI_LINEFROMPOSITION (sci-send sci SCI_GETCURRENTPOS))) + +(def (qt-plain-text-edit-cursor-column sci) + (sci-send sci SCI_GETCOLUMN (sci-send sci SCI_GETCURRENTPOS))) + +(def (qt-plain-text-edit-line-count sci) + (sci-send sci SCI_GETLINECOUNT)) + +(def (qt-plain-text-edit-line-from-position sci pos) + (sci-send sci SCI_LINEFROMPOSITION pos)) + +(def (qt-plain-text-edit-line-end-position sci line) + (sci-send sci SCI_GETLINEENDPOSITION line)) + +;; Cursor movement - simplified for now +(def (qt-plain-text-edit-move-cursor! sci op mode: (mode #f)) + (let* ((keep-anchor? (and mode (= mode QT_KEEP_ANCHOR))) + (cur-pos (sci-send sci SCI_GETCURRENTPOS)) + (new-pos cur-pos)) ;; Stub - needs full implementation + (let ((safe-pos (min new-pos (sci-send sci SCI_GETLENGTH)))) + (if keep-anchor? + (sci-send sci SCI_SETCURRENTPOS safe-pos) + (sci-send sci SCI_GOTOPOS safe-pos))))) + +(def (qt-plain-text-edit-ensure-cursor-visible! sci) + (sci-send sci SCI_SCROLLCARET)) + +(def (qt-plain-text-edit-center-cursor! sci) + (sci-send sci SCI_SCROLLCARET)) + +;; Selection +(def (qt-plain-text-edit-select-all! sci) + (sci-send sci SCI_SELECTALL)) + +(def (qt-plain-text-edit-selected-text sci) + (sci-recv-string sci SCI_GETSELTEXT)) + +(def (qt-plain-text-edit-selection-start sci) + (sci-send sci SCI_GETSELECTIONSTART)) + +(def (qt-plain-text-edit-selection-end sci) + (sci-send sci SCI_GETSELECTIONEND)) + +(def (qt-plain-text-edit-has-selection? sci) + (not (= (sci-send sci SCI_GETSELECTIONSTART) + (sci-send sci SCI_GETSELECTIONEND)))) + +(def (qt-plain-text-edit-set-selection! sci start end) + (let ((doc-len (sci-send sci SCI_GETLENGTH))) + (sci-send sci SCI_SETSEL (min start doc-len) (min end doc-len)))) + +;; Undo/Redo/Clipboard +(def (qt-plain-text-edit-undo! sci) (sci-send sci SCI_UNDO)) +(def (qt-plain-text-edit-redo! sci) (sci-send sci SCI_REDO)) +(def (qt-plain-text-edit-can-undo? sci) (not (= 0 (sci-send sci SCI_CANUNDO)))) +(def (qt-plain-text-edit-cut! sci) (sci-send sci SCI_CUT)) +(def (qt-plain-text-edit-copy! sci) (sci-send sci SCI_COPY)) +(def (qt-plain-text-edit-paste! sci) (sci-send sci SCI_PASTE)) + +;; Properties +(def (qt-plain-text-edit-set-read-only! sci ro?) + (sci-send sci SCI_SETREADONLY (if ro? 1 0))) + +(def (qt-plain-text-edit-read-only? sci) + (not (zero? (sci-send sci SCI_GETREADONLY)))) + +(def (qt-plain-text-edit-set-line-wrap! sci wrap?) + (sci-send sci SCI_SETWRAPMODE (if wrap? SC_WRAP_WORD SC_WRAP_NONE))) + +(def (qt-plain-text-edit-set-placeholder! sci text) (void)) +(def (qt-plain-text-edit-set-max-block-count! sci count) (void)) + +;; Signals +(def (qt-on-plain-text-edit-text-changed! sci handler) + (qt-on-scintilla-text-changed! sci handler)) + +;; Search +(def (qt-plain-text-edit-find-text sci text . flags) + (let* ((pos (sci-send sci SCI_GETCURRENTPOS)) + (len (sci-send sci SCI_GETTEXTLENGTH))) + (sci-send sci SCI_SETTARGETSTART pos) + (sci-send sci SCI_SETTARGETEND len) + (sci-send sci SCI_SETSEARCHFLAGS 0) + (let ((found (sci-send/string sci SCI_SEARCHINTARGET text (string-length text)))) + (if (>= found 0) found -1)))) + +;; Document management +(def (qt-plain-text-edit-set-document! sci doc) + (sci-send sci SCI_SETDOCPOINTER 0 doc)) + +(def (qt-plain-text-edit-document sci) + (sci-send sci SCI_GETDOCPOINTER)) + +(def (qt-plain-text-document-create) #f) +(def (qt-text-document-create) #f) +(def (qt-text-document-destroy! doc) (void)) + +(def (qt-text-document-modified? doc) + (let ((buf (hash-get *doc-buffer-map* doc))) + (if buf (buffer-modified buf) #f))) + +(def (qt-text-document-set-modified! doc modified?) + (let ((buf (hash-get *doc-buffer-map* doc))) + (when buf + (set! (buffer-modified buf) modified?) + (unless modified? + (let ((ed (hash-get *doc-editor-map* doc))) + (when ed (sci-send ed SCI_SETSAVEPOINT))))))) + +;; Line number area stubs +(def (qt-line-number-area-create editor) #f) +(def (qt-line-number-area-destroy! lna) (void)) +(def (qt-line-number-area-set-visible! lna visible?) (void)) +(def (qt-line-number-area-set-bg-color! lna r g b) (void)) +(def (qt-line-number-area-set-fg-color! lna r g b) (void)) + +;; Syntax highlighter stubs +(def (qt-syntax-highlighter-create doc) #f) +(def (qt-syntax-highlighter-destroy! h) (void)) +(def (qt-syntax-highlighter-add-rule! h pattern r g b bold? italic?) (void)) +(def (qt-syntax-highlighter-add-keywords! h keywords r g b bold? italic?) (void)) +(def (qt-syntax-highlighter-add-multiline-rule! h start end r g b bold? italic?) (void)) +(def (qt-syntax-highlighter-rehighlight! h) (void)) +(def (qt-syntax-highlighter-clear-rules! h) (void)) + +;; Extra selections - indicators +(def *indic-current-line* 8) +(def *indic-brace-match* 9) +(def *indic-brace-bad* 10) +(def *indic-search* 11) +(def *pending-decorations* []) + +(def (qt-extra-selections-clear! sci) + (set! *pending-decorations* []) + (let ((len (sci-send sci SCI_GETTEXTLENGTH))) + (for-each + (lambda (indic) + (sci-send sci SCI_SETINDICATORCURRENT indic) + (sci-send sci SCI_INDICATORCLEARRANGE 0 len)) + (list *indic-current-line* *indic-brace-match* *indic-brace-bad* *indic-search*)))) + +(def (qt-extra-selection-add-line! sci line bg-r bg-g bg-b) + (sci-send sci SCI_SETCARETLINEVISIBLE 1) + (sci-send sci SCI_SETCARETLINEBACK (rgb->sci bg-r bg-g bg-b))) + +(def (qt-extra-selection-add-range! sci pos len fg-r fg-g fg-b bg-r bg-g bg-b bold: (bold? #f)) + (set! *pending-decorations* + (cons (list pos len fg-r fg-g fg-b bg-r bg-g bg-b bold?) *pending-decorations*))) + +(def (qt-extra-selections-apply! sci) + (for-each + (lambda (dec) + (let ((pos (car dec)) + (len (cadr dec)) + (bg-r (list-ref dec 5)) + (bg-g (list-ref dec 6)) + (bg-b (list-ref dec 7))) + (sci-send sci SCI_SETINDICATORCURRENT *indic-brace-match*) + (sci-send sci SCI_INDICSETSTYLE *indic-brace-match* INDIC_ROUNDBOX) + (sci-send sci SCI_INDICSETFORE *indic-brace-match* (rgb->sci bg-r bg-g bg-b)) + (let* ((doc-len (sci-send sci SCI_GETLENGTH)) + (safe-pos (min pos doc-len)) + (safe-len (min len (- doc-len safe-pos)))) + (when (> safe-len 0) + (sci-send sci SCI_INDICATORFILLRANGE safe-pos safe-len))))) + *pending-decorations*) + (set! *pending-decorations* []))