Skip to content

eXist-aware completion, hover, parameter hints & exist: resource handling#12

Merged
joewiz merged 24 commits into
mainfrom
feature/completion-and-urls
Jun 5, 2026
Merged

eXist-aware completion, hover, parameter hints & exist: resource handling#12
joewiz merged 24 commits into
mainfrom
feature/completion-and-urls

Conversation

@joewiz

@joewiz joewiz commented Jun 5, 2026

Copy link
Copy Markdown
Owner

[This PR was co-authored with Claude Code. -Joe]

⚠️ This PR was merged ahead of existdb-openapi #42, #44, and #45 are released** to facilitate continued development. This PR consumes their language-service shapes.

Summary

The eXist-aware language-service layer for XQuery editors — content completion, function documentation, query evaluation — plus correct, lossless reading and writing of exist: resources (the fix that makes opening .xq/.xqm work and stops binary corruption). Stacked on the pane-polish PR; GitHub will retarget this to main once that merges.

What changed

exist: resource reading & writing (text vs binary)

  • Open XQuery as source, not by executing it. eXist stores XQuery modules as binary documents, so the resource envelope's binary flag is true for .xq/.xqm even though their content is text. Reading those from the raw streaming endpoint executed the module on GET (the "No function call details… Library Module" / OPERATION 500s). Resources are now read by whether their mime-type is textual (application/xquery, text/*, XML/JSON): text comes from the JSON envelope's UTF-8 content, and only genuinely-binary types (images, PDFs, fonts) are fetched as raw bytes.
  • Binary resources handled losslessly. A centralized ExistClient.readResource (raw bytes for binary, UTF-8 content for text) and a binary-safe streaming PUT (putResourceBytes) fix three paths that previously round-tripped binaries through text and corrupted them: download to ~/Downloads, cross-server drag-and-drop copy, and file upload (which used to skip binary files entirely — they now upload). Verified end-to-end: a PNG round-trips byte-identical and .xqm opens as intact source.
  • No spurious Problems pane when a clean exist: XQuery file is opened.

Content completion

  • Rich popup — a proposal list with function/variable icons beside a documentation panel (signature + description), like Oxygen's built-in completion.
  • Type-to-filter — a filter field seeded with the local name already typed before the caret, narrowing the list live (eXide behavior: util: → all util: functions, then wutil:wait). Down/Up steer, Enter accepts, Escape closes.
  • Snippet expansion — function completions from existdb-openapi #45 arrive as LSP snippets (util:log(${1:$priority}, ${2:$message})); accepting one inserts the expanded call and selects the first argument to type over (a dependency-free SnippetExpander handles ${n:default}/${n}/$n/choices and \$ \} \\). Plain (non-snippet) proposals fall back to dropping the caret inside ().
  • Shortcuts — Cmd/Ctrl+Alt+Slash and Ctrl+Space trigger completion, scoped to XQuery editors. (Note: macOS reserves Ctrl+Space for input-source switching at the OS level, so Cmd/Ctrl+Alt+Slash is the reliable binding there.)

Query evaluation

  • One destination-aware action. "Evaluate Query (eXist-db)" replaces the two former run entries and routes results to the destination chosen in Configure eXist-db Connections (Results pane / new editor). It's enabled only when running makes sense (an XQuery editor, or a text selection), and is bound to Cmd/Ctrl+Enter and the toolbar button.

Function documentation (F1) — rich, from LSP Markdown hover

  • F1 → eXist function documentation for the symbol under the caret, overriding Oxygen's default F1 (Help). F1=Help is a global menu accelerator, so it's intercepted with a KeyEventDispatcher that fires only while an XQuery editor is focused and otherwise leaves F1 to Oxygen.
  • Rich hover card rendered from existdb-openapi #44's LSP MarkupContent (Markdown) hover: a monospace signature, the description, a Parameters list (each $name (type) — doc), and Returns:. A small dependency-free MarkdownRenderer converts the Markdown to the Swing HTML the popup shows; long signatures wrap rather than scroll. Also covers variable references (e.g. $xxs:integer) via #44's variable-type hover. (Function hover covers eXist extension modules — util:, xmldb:, imported DB modules — not built-in fn:.)

Parameter hints (signature-help)

  • "Parameter Hints (eXist-db)" — a non-focus-stealing popup showing the enclosing call's signature with the active parameter bolded, from #44's /api/langservice/signature-help. It pops automatically as you type (, ,, ) (advancing the highlight, closing when you leave the call) and can be re-summoned via Cmd/Ctrl+Shift+Space or the context menu.

Menu labels

  • "Evaluate Query (eXist-db)", "Content Completion (eXist-db)", "Go to Definition (eXist-db)", "Hover Documentation (eXist-db)", "Parameter Hints (eXist-db)".

Test plan

  • mvn test — 78 unit tests (incl. MimeTypes.isTextual text/binary, hover Markdown parsing, signature-help parsing, MarkdownRenderer, SnippetExpander)
  • Codacy PMD clean on changed files
  • End-to-end against eXist 7 + existdb-openapi (#42 + #44 + #45 on the integration server): .xq/.xqm open as source; PNG download/upload/copy round-trips byte-identical; hover renders the full Parameters/Returns card and variable types; signature-help tracks the active parameter (incl. mid-typing and overloads); completions expand snippets
  • Manual in Oxygen: completion popup + type-to-filter + snippet first-arg select; Evaluate routes per the preference; F1 shows the rich doc card on util:log (and leaves Help elsewhere); hover on a $var shows its type; parameter hints pop while typing a call; binary image opens/downloads intact

Dependency / coordination

  • Requires existdb-openapi #42 (completion scoping), #44 (Markdown hover, variable-type hover, signature-help with overloads + lenient mid-typing resolution), and #45 (function-completion arg snippets, stacked on #42). Pin the upstream release once they land. The function hover not covering built-in fn: functions is a known upstream item; the plugin degrades gracefully (shows nothing rather than erroring).

joewiz and others added 24 commits June 5, 2026 15:59
…orrupted

The exist: URL connection fetched resources via the JSON envelope and re-encoded
the text content as UTF-8, which corrupts binary resources — so images referenced
from an Author-mode document showed "Unsupported image format". Read via the
path-in-URL streaming endpoint (GET /api/db/resource/{path}) instead, returning
the resource's raw bytes and the server's Content-Type. ExistClient gains
getResourceBytes; 404 still maps to FileNotFoundException for the Missing-File
flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…opened

Auto-validation pushed results unconditionally, so opening/validating a
problem-free exist: XQuery popped up an empty "eXist-db" Results tab. Only push
when there are problems (or to clear ones previously shown).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Trigger eXist-aware completion with Ctrl+Space and Cmd/Ctrl+Alt+Slash (bound on
  each text editor as it opens / switches to Text mode).
- Two-pane completion popup like Oxygen's built-in: the list shows the name with a
  kind icon (function/variable), and a side panel shows the selected proposal's
  signature and documentation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…longer executed

Reading every exist: resource via the raw streaming endpoint broke .xq/.xqm:
that endpoint forwards to eXist's REST servlet, which executes an XQuery main
module (HTTP 500) or rejects a library module instead of returning source. Use
the JSON envelope's UTF-8 content for text resources (correct source) and the
raw-bytes endpoint only when the resource is flagged binary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a live type-to-filter field to the eXist completion popup (eXide
behavior): the field is seeded with the local name already typed before
the caret, and narrows the proposal list by local-name prefix as you
type. Down/Up steer the list, Enter accepts, Escape closes — all while
the field keeps focus.

Also bind the completion/run shortcuts on the WHEN_ANCESTOR_OF_FOCUSED_
COMPONENT input map in addition to WHEN_FOCUSED, in case Oxygen delegates
focus to a child of the text component (helps Ctrl+Space reach us where
it isn't already consumed at the OS level), and keep the run/completion
shortcuts scoped to XQuery editors only so they don't shadow Ctrl+Space
in XML and other editors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the two editor entries ("Run in Results View (eXist)", "Run
Current Editor (eXist)") and the toolbar button with a single
"Evaluate Query with eXist-db" action that routes results to the
destination chosen in Configure eXist-db Connections (browse vs. new
editor). The action — and thus the toolbar button and menu entry — is
enabled only when running makes sense: the active editor is an XQuery
editor, or it has a text selection (tracked via a caret listener and
editor activation). Cmd/Ctrl+Enter is rebound to it.

Also rename the "eXist Completion" menu entry to "Content Completion
(eXist-db)", and make ExistAutoValidator.isXQuery public so the action
can reuse the XQuery-type check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…point

eXist stores XQuery modules as binary documents, so the JSON envelope's
`binary` flag is true for .xq/.xqm even though their content is textual
source. The previous fix branched on `binary` alone and so fetched them
from the raw streaming endpoint — which *executes* the module on GET
(HTTP 500: "No function call details… Library Module", or running a main
module) instead of returning its source.

Branch on whether the mime-type is textual instead: read XQuery/XML/JSON/
text from the JSON envelope's content, and fetch only genuinely-binary
resources (images, PDFs, fonts) as raw bytes. Adds MimeTypes.isTextual
and falls back to the extension's mime-type when the server omits one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mpletion

Accepting e.g. util:wait inserts util:wait() but left the caret after the
closing paren. Drop the caret just inside the parentheses (template-style)
when the inserted text contains "(", so the user can type arguments
straight away; non-function proposals still leave the caret at the end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- "Evaluate Query with eXist-db" -> "Evaluate Query (eXist-db)"
- "Go to Definition (eXist)" -> "Go to Definition (eXist-db)"
- "eXist Quick Documentation" -> "Function Documentation (eXist-db)"
  (it shows eXist's hover signature + docs for the symbol under the caret)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Override F1 in XQuery editors to show eXist's function documentation
  for the symbol under the caret, instead of Oxygen's default F1 (which
  opens the XQuery help page in a browser).
- Render the hover as a small HTML card — a bold monospace signature above
  a sans-serif description — replacing the plain text area, closer to
  eXide's inline F1 doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pload

Binary resources (images, PDFs, fonts) were round-tripped through the
JSON envelope's text content, corrupting them. Centralize a binary-correct
read in ExistClient.readResource (raw bytes for genuinely-binary types,
UTF-8 content for textual ones — including XQuery, which eXist flags as
binary but whose source must not run through the module-executing
streaming endpoint), and add ExistClient.putResourceBytes (a binary-safe
streaming PUT). Apply both to:

- Download to ~/Downloads (was writing text bytes for binaries),
- cross-server drag-and-drop copy (was round-tripping through text),
- file upload (previously skipped binary files entirely — now stored).

ExistURLConnection now reuses readResource, deduplicating the open path.
Verified end-to-end: a PNG round-trips byte-identical and .xqm still
opens as source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The input-map binding couldn't beat Oxygen's global F1=Help menu
accelerator. Use a KeyEventDispatcher, which sees the key before menu
accelerators: when an XQuery editor's text component is focused, F1 fires
eXist Function Documentation and the event is consumed so Help doesn't
also open; otherwise F1 falls through to Oxygen unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nature-help)

Consume existdb-openapi #44's richer language-service shapes:

- Hover `contents` is now LSP MarkupContent ({kind:"markdown", value}). Parse
  the Markdown and render it as a rich card via a new dependency-free
  MarkdownRenderer (fenced code → monospace signature, **bold** sections,
  `-` bullets, inline `code`), so Function Documentation shows the full
  Parameters / Returns the server provides — replacing the hand-rolled
  flat-text formatting.
- Add ExistClient.signatureHelp + a SignatureHelpAction "Parameter Hints
  (eXist-db)": a non-focus-stealing popup showing the enclosing call's
  signature with the active parameter bolded. It pops automatically as you
  type "(", ",", ")" and can be re-summoned via Cmd/Ctrl+Shift+Space or the
  context menu.

Verified against the integration server (#42 + #44 on :18081): hover
renders the full card; signature-help tracks the active parameter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Render the hover's signature/code block as a wrapping monospace block and
  disable the popup's horizontal scrollbar, so a long signature reflows to
  the popup width instead of spilling off the right edge.
- Use "eXist-db" (not "eXist") in the user-facing status/error messages for
  hover, completion, and query results, matching the menu/UI naming.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
existdb-openapi #45 returns function completions as LSP snippets
(insertTextFormat 2), e.g. util:log(${1:$priority}, ${2:$message}). Add a
dependency-free SnippetExpander that expands ${n:default}/${n}/$n/choices
and the \$ \} \\ escapes, and have completion insert the expanded text and
select the first argument so the user can type over it — instead of
inserting the raw ${...} markers. Non-snippet proposals keep the prior
caret-inside-parens behavior. Full Tab-cycling between stops is a later
enhancement.

ExistClient.Completion now carries insertTextFormat (with isSnippet()).
Verified against the integration server (#42 + #44 + #45 on :18081).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The signature-help popup was positioned above the caret's line, so a call
on the first line (e.g. typing util:log( at the top of a document) put the
hint off-screen above the editor — it appeared to do nothing. Fall back to
below the caret line when the above position would be above the component's
top edge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…edback

The signature-help hint was wired to hide on the editor's focusLost — but
the popup itself can take focus when shown, so the editor lost focus and
the hint hid itself the instant it appeared (nothing visible). Remove that
self-dismissing listener; the hint is already re-evaluated/closed on the
next relevant keystroke. Also give the manual trigger (menu / shortcut)
status feedback — "no function call at the caret", a connect prompt, or the
error — so an empty result no longer looks like a no-op. The auto-trigger
while typing stays silent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Swing's default <code> renders smaller than the 11px body text, so in the
variable-type hover card ("$x as xs:integer") the surrounding "as" looked
oversized. Render inline `code` as an explicit 11px monospace span so the
sizes match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
signature-help needs the exact 0-based column (the position right after the
call's "("), but Oxygen's getColumnOfOffset()-1 produced an off-by-one, so
the server found no enclosing call and returned null ("no function call at
the caret") — even though the same text resolved fine when called directly.
Hover tolerated the error by snapping to the surrounding token; signature-
help doesn't. Compute line/column directly from the caret offset and text
instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Xist-db)"

The action shows hover docs for any symbol (a function's signature, a
variable's type — LSP textDocument/hover), not just functions, so the
narrower name was misleading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cus is lost

The signature-help popup is a heavyweight, always-on-top window, and with
the focus-loss listener gone it never dismissed — it lingered above other
windows after the caret moved off the function. Restore dismissal: a caret
listener re-evaluates while a hint is up (refreshing the active parameter
and closing when the caret leaves the call), and a focus listener hides it
when the editor loses focus — ignoring transient focus loss (to a popup/
menu) so the hint can't dismiss itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set each action's accelerator so the contextual menu displays its shortcut
(completion advertises the reliable ⌥⌘/, since macOS eats ⌃Space), and
order the entries by the typical flow while writing a query: Content
Completion, Parameter Hints, Hover Documentation, Go to Definition,
Evaluate Query.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The component focus listener ignores transient focus loss (so the popup
can't self-dismiss), but switching apps (e.g. Apple menu → About This Mac)
is exactly a transient loss, so the hint lingered above other apps. Also
hide when the editor's top-level window loses focus — a precise signal for
leaving the window/app that a non-focusable popup doesn't trigger on its
own appearance. The window-focus listener is attached while a hint is shown
and removed when it hides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joewiz joewiz merged commit 1941620 into main Jun 5, 2026
4 checks passed
@joewiz joewiz deleted the feature/completion-and-urls branch June 5, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant