Repository navigation
Preserve syntax highlighting when copying BibTeX in Source tab (#17392) - #17441
adityamukesh wants to merge 20 commits into
Conversation
PR Summary by QodoPreserve BibTeX syntax highlighting when copying from the Source tab
AI Description
Diagram
High-Level Assessment
Files changed (9)
|
Code Review by Qodo
1. Pasted colors differ from the Source tab
|
…TML export codeArea.copy() already put RTF, HTML and plain text on the clipboard, so the extra HTML copy repeated the same export. The default HTML export writes one paragraph per line without a font, which loses indentation and alignment. BibTeXCodeTextModel replaces the model's HTML export handler with BibTeXHtmlExportFormatHandler, which writes the selection as one monospace <pre> block with the theme's syntax colors. Since the handler belongs to the model, every copy route uses it: Ctrl+C, Ctrl+Insert, cut, and the context menu, in the Source tab, the citation relations popup, and the import dialog. The copy hooks in SourceTab and CodeAreaKeyBindings, the BibTeX source clipboard marker, and the Markdown editor special case are no longer needed.
Pages pastes the RTF flavor whenever the clipboard has one, even though JavaFX lists HTML first. The default RTF export names Helvetica, so the pasted source still lost its alignment. BibTeXRtfExportFormatHandler writes the selection in Courier New, marked as \fmodern so that applications without it pick another monospace font, with \line breaks and the theme's syntax colors. BibTeXCodeTextModel registers it in place of the default RTF handler.
|
@LoayTarek5 thanks, you were right that
Before / after, copied in Light theme and pasted into Pages: Written with help from Claude Code. |
|
Please do not mark resolved by yourself |
There was a problem hiding this comment.
@adityamukesh this is much better now, thanks, also could you add a LibreOffice paste too? it may take the HTML instead of the rtf.
| if (attributes.isBold()) { | ||
| css.add("font-weight: bold;"); | ||
| } | ||
| if (attributes.isItalic()) { | ||
| css.add("font-style: italic;"); | ||
| } | ||
| StringJoiner decoration = new StringJoiner(" "); | ||
| if (attributes.isUnderline()) { | ||
| decoration.add("underline"); | ||
| } | ||
| if (attributes.isStrikeThrough()) { | ||
| decoration.add("line-through"); | ||
| } |
There was a problem hiding this comment.
the BibTeX css only sets the fill, so i think these branches (here and in the rtf handler) can go, and also the html handler could reuse RunCollector from the rtf one, right?
| } | ||
|
|
||
| private static void export(StyledTextModel model, StyleResolver resolver, TextPos start, TextPos end, Writer writer) throws IOException { | ||
| writer.write("<html><head><meta charset=\"utf-8\"></head><body><pre style=\"font-family: monospace;\">"); |
There was a problem hiding this comment.
a newline right after <pre> gets dropped, so a selection starting at a line end loses its first break, also using 'Courier New', monospace would match the rtf font
| String rtf = clipboard.getRtf(); | ||
| assertTrue(rtf.startsWith("{\\rtf1\\ansi\\uc1\\deff0{\\fonttbl{\\f0\\fmodern Courier New;}}")); | ||
| assertEquals(3, rtf.split("\\\\line\n", -1).length - 1); | ||
| assertTrue(rtf.contains("\\line\n ")); | ||
| assertTrue(rtf.contains(" = ")); | ||
| }); | ||
| } |
There was a problem hiding this comment.
could these compare the whole output with assertEquals? contains(" = ") passes for many wrong outputs.
|
@adityamukesh, any updates? |
|
I am working on the requested testing and will have an update for you soon.
…On Fri, 23 Oct, 2026, 2:51 pm Loay Tarek Mostafa, ***@***.***> wrote:
*LoayTarek5* left a comment (JabRef/jabref#17441)
<#17441 (comment)>
@adityamukesh <https://github.com/adityamukesh>, any updates?
—
Reply to this email directly, view it on GitHub
<#17441?email_source=notifications&email_token=BWZ65ALB742Y4MF6LBQIG335VMPI5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMMZWGE3DQMJWG422M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-6361681675>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/BWZ65ALBY4X5WAGUQV7DX7L5VMPI5AVCNFSNUABEKJSXA33TNF2G64TZHMYTONRTGQYDOMJ3JFZXG5LFHM2TOOJYGEZTOOJWGSQXMAQ>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/BWZ65AM4NEHIV6IGKJL53KL5VMPI5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMMZWGE3DQMJWG422M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/BWZ65AIYNTDHIOEJU2YHYVL5VMPI5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMMZWGE3DQMJWG422M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
The BibTeX stylesheets only set the fill, so both exports keep only the text color; the bold, italic, underline and strike-through branches are gone, and the HTML export reuses the RTF export's run collection. HTML drops a line break directly after <pre>, so the HTML export writes one there; a selection that starts at a line end keeps its first break. The <pre> uses 'Courier New', monospace, like the RTF export. The RTF tests compare the whole output.
|
hi sir , i have a update for u : is kept, and the HTML uses 'Courier New', monospace like the RTF |
|
@LoayTarek5 here is the LibreOffice paste you asked for (LibreOffice Writer 26.8.1 on macOS, JabRef in Light theme). You were right that LibreOffice takes the HTML here, not the RTF. main: every line becomes its own paragraph, the indentation and the spaces before this branch: pasted as "Preformatted Text" in Courier New, so the indentation, the aligned Pasted with LibreOffice's own paste command from the clipboard contents JabRef produces; screenshots made with help from Claude Code. |
|
@LoayTarek5 please review it. |
|
The failing “External services tests” check seems unrelated to this PR. It fails on main as well because of the external services. Could a maintainer re-run it later? |





Summary
Copying from the BibTeX source tab now puts monospace HTML and RTF on the clipboard, so pasted source keeps its indentation, alignment, and syntax colors in rich-text applications.
BibTeXCodeAreausesBibTeXCodeTextModel, which replaces the model's default HTML and RTF export handlers.BibTeXHtmlExportFormatHandlerwrites one<pre>block with a monospace font, andBibTeXRtfExportFormatHandlerwrites Courier New (marked\fmodern, so other applications fall back to a monospace font) with\linebreaks. Both keep the colors of the current theme. Because the handlers belong to the model, every copy route uses them: Ctrl+C, Ctrl+Insert, cut, and the context menu, in the Source tab, the citation relations popup, and the import dialog.The default HTML export already put syntax colors on the clipboard, but wrote one paragraph per line in a proportional font. Pages pastes the RTF version whenever it is on the clipboard, so the RTF needed the same fix.
Analogies
Like honey retaining its color and chocolate retaining its character, copied BibTeX keeps its styling; the moon represents theme-dependent appearance.
Steps to test
=signs lined up, and the syntax colors. Repeat with the Source tab's context menu and with Ctrl + Insert.&,<,>,{,},\, and non-ASCII characters. The pasted text should match the source.Source tab with the example entry (JabRef in dark theme):
Before, on
main: copied in Light theme and pasted into Pages. The font is proportional, so the=signs do not line up:After, on this branch: copied in Light theme and pasted into Pages:
Both screenshots were made by copying through
BibTeXCodeAreawith JabRef's Light theme stylesheets onto the real macOS clipboard, then pasting into Pages. In dark theme the copied colors are the dark-theme colors, so field values are near-white on a white page; whether pasted text should use a fixed light palette is an open question.Related issues and pull requests
Closes #17392
Verification
c659f5831replaces the earlier copy hooks with the model-level HTML export,7d40fbb4dadds the RTF export, and860f74424applies the formatter's line split that the format check asked for.On
7d40fbb4d, locally with Java 25::jabgui:checkstyleMainand:jabgui:checkstyleTestpass, andBibTeXHtmlExportFormatHandlerTest(5 tests),BibTeXRtfExportFormatHandlerTest(3 tests),BibTeXHighlighterTest, and theentryeditor,importer, andfieldeditorsGUI tests pass. The HTML tests fail when the default HTML handler is used instead. Full CI on this head is in progress.The current head has not yet been tested by hand in running JabRef; the screenshots come from the same
BibTeXCodeAreacopy, as described above.AI usage
The original description disclosed Google Antigravity (Gemini 2.5), classified as AIL2. OpenAI Codex also assisted with the subsequent PR work and this description update (the exact model identifier for this session is unavailable). The exact model identifiers for the earlier tools and the final influence classification still require contributor confirmation. Claude Code (model claude-opus-5-5) assisted with merging
mainand restoring theBibTeXHighlighterimport in48e7516, with sorting theCHANGELOG.mdentry ina060758, with the matching updates of this description and checklist, with drafting the re-review comment, and with capturing the pasted-result screenshot. It also wrotec659f5831(model-level HTML export, replacing the copy hooks) and7d40fbb4d(monospace RTF export) with their tests, made the before/after screenshots, rewrote this description, and drafted the reply to the review.The contributor must confirm review, understanding, and ownership of the current changes before the reviewed-policy compliance tag is restored. The full checklist below records verified items and outstanding work; it is not a claim that the PR is fully merge-ready.
AI CHECKLIST.md walkthrough
Checked against the current PR diff and the linked CI results. Verification-command items marked complete use equivalent CI gates; the library check excludes lint tasks that passed in separate jobs, and OpenRewrite ran with a clean-worktree check. Those full CI gates cover the earlier head
69a32d8; the local checks of7d40fbb4dare documented above, and full CI of the current head860f74424is in progress. Unchecked items require further verification or contributor confirmation.Code checklist
1. Code self-review
Read your own diff once, top to bottom, and confirm each point.
Nullability and control flow
== null/!= nullchecks — JSpecify annotations (@NullMarked,@Nullable,@NonNull) used instead.Objects.requireNonNull(...)— nullability expressed via JSpecify annotations.@NullMarked(org.jspecify.annotations.NullMarked).@NullMarked(org.jspecify.annotations.NullMarked).Optionalconsumed withifPresent/ifPresentOrElse/map/orElseThrow— neverorElse(unusedValue)nor anisPresent()+get()block.StringUtil.isBlank(...)used instead ofs == null || s.isBlank().Exceptions
catch (Exception e)— only specific exceptions are caught.throw new RuntimeException(...)/IllegalStateException(...)— these tear down the whole application.LOGGER.info("...", e)), not concatenated into the message string.Style and idioms
BibEntryobjects built with withers (withField, notsetField).List.of()/Map.of()/Set.of(),Path.of(),SequencedCollection/SequencedSet, text blocks.Pattern.compile(...)constant, notString.matches(...).org.jabref.logic.util.BackgroundTask, notnew Thread().///) uses Markdown syntax, not JavaDoc inline tags:`code`instead of{@code},[ClassName]instead of{@link}.User-facing text
Localization.langin Java,%prefix in FXML).!; labels do not end with:."...: %0"), not string concatenation.Security
text/htmlresponse — including exception/error messages, not just the success body (XSS).Tests
org.jabref.model/org.jabref.logichave added or updated tests.assertEquals), use plain JUnit asserts (not AssertJ), have no@DisplayName, do not catch exceptions (let them propagate so JUnit reports setup/teardown failures directly), and use@TempDirinstead of manual temp directories.2. Verification commands
Run in this order — cheapest first. Each must pass.
./gradlew :jablib:check(or./gradlew checkfor all modules)../gradlew checkstyleMain checkstyleTest checkstyleJmh../gradlew modernizer../gradlew --no-configuration-cache :rewriteDryRunreports no changes (run./gradlew rewriteRunto fix)../gradlew javadoc.npx markdownlint-cli2 "docs/**/*.md" "*.md"(only if Markdown changed).npm ci && npm run textlintreports no misspellings (only if Markdown changed).rewriteRun:docker run -v $(pwd):/github/workspace ghcr.io/leventebajczi/intellij-format:master "*.java" "" ".idea/codeStyles/Project.xml".3. Documentation
CHANGELOG.mdentry added if the change is visible to the user (end-user wording, no extra blank lines, sorted in next to existing entries about the same component/feature). Link the issue if one exists; link the PR only when no issue exists. UseTODOas the placeholder when neither is known yet — never a fake number. No entry for fixes to changes that were themselves introduced after the last release (feature only in## [Unreleased]) — update the existing unreleased entry instead if needed.TODO(nocloses/fixesfor merely-similar issues).docs/requirements/<area>.mdif the change is a new feature or significant bug fix (skip for refactors, minor fixes, and internal changes).docs/updated if behavior or architecture changed.4. Pull request
.github/PULL_REQUEST_TEMPLATE.md, every section filled.[x],[ ], or[/].gh pr create --body-file <file>(not--body).CHANGELOG.mdused aTODOplaceholder (no issue confidently identified yet — an existing issue link always stays), the PR was opened as draft, the placeholder was replaced with the real PR-number link after PR creation, committed and pushed, and only then was the PR marked ready for review. If an issue is identified or created later, the link is switched to the issue.Checklist
CHANGELOG.mddescribing the change from the user's point of view (if the change is visible to the user)The existing copyright/license declaration is retained. The user documentation describes the Source tab but not copying from it, so nothing there is outdated. The AI influence level remains open.