From ba316c3523a5e0564f085136258308bb0b8ecbff Mon Sep 17 00:00:00 2001 From: Tony Germano Date: Sat, 10 Oct 2026 12:13:47 -0400 Subject: [PATCH 1/2] Add tests for MirthPlainDocument line breaks MirthPlainDocument differs from PlainDocument mainly in treating a lone CR as a line break, so CR-delimited HL7 shows one segment per line, while CRLF stays a single break. Pin down that behavior before the class is rewritten: line boundaries for CR, LF, CRLF and LFCR, joining and splitting CRLF through edits, one root element change per insert, removal across lines, undo/redo, the unchanged text, and the tab size of 4. Also cover text in other scripts: right-to-left Hebrew and Arabic lines with their bidi levels, mixed-direction lines, bidi control characters, CJK and supplementary characters, and input method composed text. Issue: https://github.com/OpenIntegrationEngine/engine/issues/473 Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Tony Germano --- .../ui/components/MirthPlainDocumentTest.java | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 client/src/test/java/com/mirth/connect/client/ui/components/MirthPlainDocumentTest.java diff --git a/client/src/test/java/com/mirth/connect/client/ui/components/MirthPlainDocumentTest.java b/client/src/test/java/com/mirth/connect/client/ui/components/MirthPlainDocumentTest.java new file mode 100644 index 0000000000..9e9d945781 --- /dev/null +++ b/client/src/test/java/com/mirth/connect/client/ui/components/MirthPlainDocumentTest.java @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Tony Germano + +package com.mirth.connect.client.ui.components; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.text.AttributedString; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.PlainDocument; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.undo.UndoManager; + +import org.junit.Test; +import org.syntax.jedit.MirthPlainDocument; + +public class MirthPlainDocumentTest { + + @Test + public void testTabSize() { + assertEquals(4, new MirthPlainDocument().getProperty(PlainDocument.tabSizeAttribute)); + } + + @Test + public void testLoneCarriageReturn() throws Exception { + assertLines(docWith("a\rb"), "a\r", "b"); + } + + @Test + public void testLineFeed() throws Exception { + assertLines(docWith("a\nb"), "a\n", "b"); + } + + @Test + public void testCrLf() throws Exception { + assertLines(docWith("a\r\nb"), "a\r\n", "b"); + } + + @Test + public void testLfCr() throws Exception { + assertLines(docWith("a\n\rb"), "a\n", "\r", "b"); + } + + @Test + public void testMixed() throws Exception { + assertLines(docWith("\r\r\n"), "\r", "\r\n", ""); + } + + @Test + public void testTextIsUnchanged() throws Exception { + MirthPlainDocument doc = docWith("a\rb\r\nc\n"); + assertEquals("a\rb\r\nc\n", doc.getText(0, doc.getLength())); + } + + @Test + public void testHl7() throws Exception { + MirthPlainDocument doc = docWith("MSH|^~\\&|A\rPID|1\rPV1|1\r"); + assertLines(doc, "MSH|^~\\&|A\r", "PID|1\r", "PV1|1\r", ""); + } + + @Test + public void testLineFeedAfterCarriageReturnJoins() throws Exception { + MirthPlainDocument doc = docWith("a\rb"); + DocumentEvent.ElementChange change = captureInsert(doc, 2, "\n"); + assertLines(doc, "a\r\n", "b"); + assertEquals(1, change.getChildrenRemoved().length); + assertEquals(1, change.getChildrenAdded().length); + } + + @Test + public void testInsertBetweenCrLfSplits() throws Exception { + MirthPlainDocument doc = docWith("a\r\nb"); + DocumentEvent.ElementChange change = captureInsert(doc, 2, "x"); + assertLines(doc, "a\r", "x\n", "b"); + assertEquals(0, change.getIndex()); + assertEquals(1, change.getChildrenRemoved().length); + assertEquals(2, change.getChildrenAdded().length); + } + + @Test + public void testInsertMultipleLinesSingleChange() throws Exception { + MirthPlainDocument doc = docWith("ab"); + DocumentEvent.ElementChange change = captureInsert(doc, 1, "1\r2\r\n3\n4"); + assertLines(doc, "a1\r", "2\r\n", "3\n", "4b"); + assertEquals(0, change.getIndex()); + assertEquals(1, change.getChildrenRemoved().length); + assertEquals(4, change.getChildrenAdded().length); + } + + @Test + public void testRemoveAcrossLines() throws Exception { + MirthPlainDocument doc = docWith("a\rb\rc"); + doc.remove(1, 2); + assertLines(doc, "a\r", "c"); + } + + @Test + public void testUndoRedo() throws Exception { + MirthPlainDocument doc = new MirthPlainDocument(); + UndoManager undo = new UndoManager(); + doc.addUndoableEditListener(undo); + doc.insertString(0, "a\rb", null); + undo.undo(); + assertLines(doc, ""); + undo.redo(); + assertLines(doc, "a\r", "b"); + } + + @Test + public void testRightToLeftLines() throws Exception { + // Hebrew, Latin, Arabic + String text = "שלום\rhello\r\nمرحبا"; + MirthPlainDocument doc = docWith(text); + assertLines(doc, "שלום\r", "hello\r\n", "مرحبا"); + assertEquals(text, doc.getText(0, doc.getLength())); + assertEquals(1, bidiLevel(doc, 0)); + assertEquals(0, bidiLevel(doc, 5)); + assertEquals(1, bidiLevel(doc, 12)); + } + + @Test + public void testMixedDirectionLines() throws Exception { + MirthPlainDocument doc = docWith("abc אבג def\rאב 123\r"); + assertLines(doc, "abc אבג def\r", "אב 123\r", ""); + // Left-to-right line with an embedded Hebrew word + assertEquals(0, bidiLevel(doc, 0)); + assertEquals(1, bidiLevel(doc, 4)); + assertEquals(0, bidiLevel(doc, 9)); + // Right-to-left line with embedded digits + assertEquals(1, bidiLevel(doc, 12)); + assertEquals(2, bidiLevel(doc, 15)); + } + + @Test + public void testInsertRightToLeftIntoLine() throws Exception { + MirthPlainDocument doc = docWith("abc\rdef"); + doc.insertString(4, "אב", null); + assertLines(doc, "abc\r", "אבdef"); + assertEquals(0, bidiLevel(doc, 0)); + assertEquals(1, bidiLevel(doc, 4)); + assertEquals(2, bidiLevel(doc, 6)); + } + + @Test + public void testBidiControlCharacters() throws Exception { + // RLE ... PDF, RLI ... PDI, RLM, LRM + String text = "‫abc‬\r⁧xyz⁩\r\n‏א‎q"; + MirthPlainDocument doc = docWith(text); + assertLines(doc, "‫abc‬\r", "⁧xyz⁩\r\n", "‏א‎q"); + assertEquals(text, doc.getText(0, doc.getLength())); + } + + @Test + public void testCjkAndSupplementaryCharacters() throws Exception { + // Chinese, Japanese, Korean, a CJK Extension B ideograph and an emoji (surrogate pairs) + String text = "中文\r日本語\r\n한국어\n𠀀\r😀"; + MirthPlainDocument doc = docWith(text); + assertLines(doc, "中文\r", "日本語\r\n", "한국어\n", "𠀀\r", "😀"); + assertEquals(text, doc.getText(0, doc.getLength())); + } + + @Test + public void testComposedText() throws Exception { + MirthPlainDocument doc = docWith("a\rbc\rd"); + SimpleAttributeSet composed = new SimpleAttributeSet(); + composed.addAttribute(StyleConstants.ComposedTextAttribute, new AttributedString("日本")); + + // Input method text in progress becomes its own element within the line + doc.insertString(3, "日本", composed); + assertLines(doc, "a\r", "b日本c\r", "d"); + Element line = doc.getDefaultRootElement().getElement(1); + assertFalse(line.isLeaf()); + assertEquals(3, line.getElementCount()); + + // Committing removes the composed text and restores a plain line + doc.remove(3, 2); + assertLines(doc, "a\r", "bc\r", "d"); + assertTrue(doc.getDefaultRootElement().getElement(1).isLeaf()); + } + + private static int bidiLevel(MirthPlainDocument doc, int offset) { + Element bidiRoot = doc.getBidiRootElement(); + Element run = bidiRoot.getElement(bidiRoot.getElementIndex(offset)); + return StyleConstants.getBidiLevel(run.getAttributes()); + } + + private static MirthPlainDocument docWith(String text) throws BadLocationException { + MirthPlainDocument doc = new MirthPlainDocument(); + doc.insertString(0, text, null); + return doc; + } + + private static DocumentEvent.ElementChange captureInsert(MirthPlainDocument doc, int offset, String text) throws BadLocationException { + List changes = new ArrayList<>(); + DocumentListener listener = new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + changes.add(e.getChange(doc.getDefaultRootElement())); + } + + @Override + public void removeUpdate(DocumentEvent e) {} + + @Override + public void changedUpdate(DocumentEvent e) {} + }; + doc.addDocumentListener(listener); + doc.insertString(offset, text, null); + doc.removeDocumentListener(listener); + assertEquals(1, changes.size()); + assertNotNull(changes.get(0)); + return changes.get(0); + } + + private static void assertLines(MirthPlainDocument doc, String... expected) throws BadLocationException { + Element root = doc.getDefaultRootElement(); + List lines = new ArrayList<>(); + for (int i = 0; i < root.getElementCount(); i++) { + Element line = root.getElement(i); + int end = Math.min(line.getEndOffset(), doc.getLength()); + lines.add(doc.getText(line.getStartOffset(), end - line.getStartOffset())); + } + assertEquals(Arrays.asList(expected), lines); + } +} From 1264fd0e973b133e6e5054cc7a4b88a234053d80 Mon Sep 17 00:00:00 2001 From: Tony Germano Date: Sat, 10 Oct 2026 12:14:15 -0400 Subject: [PATCH 2/2] Reimplement MirthPlainDocument on top of PlainDocument MirthPlainDocument was a copy of the Java 1.5 PlainDocument source with its Sun proprietary header, which is not compatible with the project's license. The only behavior it changed was to break lines on a lone CR as well as LF, plus a default tab size of 4 instead of 8. Extend PlainDocument instead and keep only those two changes. PlainDocument builds its line map from a single read of the inserted text, splitting after each LF. The document's content now reports lone CRs as LFs for that one read, on a copy of the text, so PlainDocument puts the breaks in the same places while the stored text stays unchanged and each insert still produces a single root element change. The translation is tied to the inserting thread, since getText does not take the document lock and another thread could otherwise make that read. Drop the constructor that takes a Content, since nothing uses it and the line breaks depend on the document's own content. Move the class out of the third-party org.syntax.jedit package into com.mirth.connect.client.ui.components, next to MirthSyntaxTextArea, which uses it through SyntaxDocument. Issue: https://github.com/OpenIntegrationEngine/engine/issues/473 Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Tony Germano --- .../ui/components/MirthPlainDocument.java | 97 +++++ .../org/syntax/jedit/MirthPlainDocument.java | 342 ------------------ .../java/org/syntax/jedit/SyntaxDocument.java | 2 + .../ui/components/MirthPlainDocumentTest.java | 1 - 4 files changed, 99 insertions(+), 343 deletions(-) create mode 100644 client/src/main/java/com/mirth/connect/client/ui/components/MirthPlainDocument.java delete mode 100644 client/src/main/java/org/syntax/jedit/MirthPlainDocument.java diff --git a/client/src/main/java/com/mirth/connect/client/ui/components/MirthPlainDocument.java b/client/src/main/java/com/mirth/connect/client/ui/components/MirthPlainDocument.java new file mode 100644 index 0000000000..f2f12a4eda --- /dev/null +++ b/client/src/main/java/com/mirth/connect/client/ui/components/MirthPlainDocument.java @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Tony Germano + +package com.mirth.connect.client.ui.components; + +import java.util.Arrays; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.GapContent; +import javax.swing.text.PlainDocument; +import javax.swing.text.Segment; + +/** + * A {@link PlainDocument} that also treats a lone carriage return as a line + * break, so CR-delimited content such as HL7 is shown one segment per line. + * A CR immediately followed by LF is still a single line break. The text + * itself is stored unchanged. The default tab size is 4. + */ +public class MirthPlainDocument extends PlainDocument { + + /** + * Constructs a plain text document backed by a gap buffer that recognizes + * carriage returns as line breaks. + */ + public MirthPlainDocument() { + super(new LineBreakContent()); + putProperty(tabSizeAttribute, 4); + } + + @Override + protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { + /* + * PlainDocument builds the line map from a single read of the inserted + * range, splitting after each LF. Presenting lone CRs as LFs for that + * read gives them the same treatment while producing one element + * change for the whole insert. + */ + LineBreakContent content = (LineBreakContent) getContent(); + content.translatingThread = Thread.currentThread(); + try { + super.insertUpdate(chng, attr); + } finally { + content.translatingThread = null; + } + } + + /** + * Gap buffer content that can report lone CRs as LFs for a single read. + */ + private static class LineBreakContent extends GapContent { + + private static final long serialVersionUID = 1L; + + /** + * When set, the next read on this thread reports lone CRs as LFs. + * Every other read (getText, painting, tokenizing, search, saving) + * must see the real text, so the translation is limited to the read + * PlainDocument makes to find line breaks. It is tied to the inserting + * thread because AbstractDocument.getText does not take the document + * lock, so another thread may read while an insert is in progress. It + * is cleared on first use because AbstractDocument.insertUpdate reads + * the inserted text again afterwards for its bidi and multibyte + * checks, and those should see the real characters too. + */ + private transient Thread translatingThread; + + @Override + public void getChars(int where, int len, Segment chars) throws BadLocationException { + super.getChars(where, len, chars); + if (translatingThread != Thread.currentThread()) { + return; + } + translatingThread = null; + + /* + * The segment may point into the gap buffer, so translate a copy. + * Pointing the segment at the copy is allowed here: GapContent + * does the same when the range spans the gap. + */ + char[] copy = null; + int end = chars.offset + chars.count; + for (int i = chars.offset; i < end; i++) { + if (chars.array[i] == '\r' && (i + 1 == end || chars.array[i + 1] != '\n')) { + if (copy == null) { + copy = Arrays.copyOfRange(chars.array, chars.offset, end); + } + copy[i - chars.offset] = '\n'; + } + } + if (copy != null) { + chars.array = copy; + chars.offset = 0; + } + } + } +} diff --git a/client/src/main/java/org/syntax/jedit/MirthPlainDocument.java b/client/src/main/java/org/syntax/jedit/MirthPlainDocument.java deleted file mode 100644 index 255622a737..0000000000 --- a/client/src/main/java/org/syntax/jedit/MirthPlainDocument.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * @(#)PlainDocument.java 1.43 03/12/19 - * - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. - * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. - */ -package org.syntax.jedit; - -import java.util.Vector; - -import javax.swing.text.AbstractDocument; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.Document; -import javax.swing.text.Element; -import javax.swing.text.GapContent; -import javax.swing.text.Segment; -import javax.swing.text.StyleConstants; - -/** - * A plain document that maintains no character attributes. The default element - * structure for this document is a map of the lines in the text. The Element - * returned by getDefaultRootElement is a map of the lines, and each child - * element represents a line. This model does not maintain any character level - * attributes, but each line can be tagged with an arbitrary set of attributes. - * Line to offset, and offset to line translations can be quickly performed - * using the default root element. The structure information of the - * DocumentEvent's fired by edits will indicate the line structure changes. - *

- * The default content storage management is performed by a gapped buffer - * implementation (GapContent). It supports editing reasonably large documents - * with good efficiency when the edits are contiguous or clustered, as is - * typical. - *

- * Warning: Serialized objects of this class will not be - * compatible with future Swing releases. The current serialization support is - * appropriate for short term storage or RMI between applications running the - * same version of Swing. As of 1.4, support for long term storage of all - * JavaBeansTM has been added to the - * java.beans package. Please see {@link java.beans.XMLEncoder}. - * - * @author Timothy Prinzing - * @version 1.43 12/19/03 - * @see Document - * @see AbstractDocument - */ -public class MirthPlainDocument extends AbstractDocument { - - /** - * Name of the attribute that specifies the tab size for tabs contained in - * the content. The type for the value is Integer. - */ - public static final String tabSizeAttribute = "tabSize"; - - /** - * Name of the attribute that specifies the maximum length of a line, if - * there is a maximum length. The type for the value is Integer. - */ - public static final String lineLimitAttribute = "lineLimit"; - - /** - * Constructs a plain text document. A default model using - * GapContent is constructed and set. - */ - public MirthPlainDocument() { - this(new GapContent()); - } - - /** - * Constructs a plain text document. A default root element is created, and - * the tab size set to 4. - * - * @param c - * the container for the content - */ - public MirthPlainDocument(Content c) { - super(c); - putProperty(tabSizeAttribute, 4); - defaultRoot = createDefaultRoot(); - } - - /** - * Inserts some content into the document. Inserting content causes a write - * lock to be held while the actual changes are taking place, followed by - * notification to the observers on the thread that grabbed the write lock. - *

- * This method is thread safe, although most Swing methods are not. Please - * see Threads - * and Swing for more information. - * - * @param offs - * the starting offset >= 0 - * @param str - * the string to insert; does nothing with null/empty strings - * @param a - * the attributes for the inserted content - * @exception BadLocationException - * the given insert position is not a valid position within - * the document - * @see Document#insertString - */ - public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { - // fields don't want to have multiple lines. We may provide a - // field-specific - // model in the future in which case the filtering logic here will no - // longer - // be needed. - Object filterNewlines = getProperty("filterNewlines"); - if ((filterNewlines instanceof Boolean) && filterNewlines.equals(Boolean.TRUE)) { - if ((str != null) && (str.indexOf('\n') >= 0)) { - StringBuffer filtered = new StringBuffer(str); - int n = filtered.length(); - for (int i = 0; i < n; i++) { - if (filtered.charAt(i) == '\n') { - filtered.setCharAt(i, ' '); - } - } - str = filtered.toString(); - } - } - super.insertString(offs, str, a); - } - - /** - * Gets the default root element for the document model. - * - * @return the root - * @see Document#getDefaultRootElement - */ - public Element getDefaultRootElement() { - return defaultRoot; - } - - /** - * Creates the root element to be used to represent the default document - * structure. - * - * @return the element base - */ - protected AbstractElement createDefaultRoot() { - BranchElement map = (BranchElement) createBranchElement(null, null); - Element line = createLeafElement(map, null, 0, 1); - Element[] lines = new Element[1]; - lines[0] = line; - map.replace(0, 0, lines); - return map; - } - - /** - * Get the paragraph element containing the given position. Since this - * document only models lines, it returns the line instead. - */ - public Element getParagraphElement(int pos) { - Element lineMap = getDefaultRootElement(); - return lineMap.getElement(lineMap.getElementIndex(pos)); - } - - /** - * Updates document structure as a result of text insertion. This will - * happen within a write lock. Since this document simply maps out lines, we - * refresh the line map. - * - * @param chng - * the change event describing the dit - * @param attr - * the set of attributes for the inserted text - */ - protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { - removed.removeAllElements(); - added.removeAllElements(); - BranchElement lineMap = (BranchElement) getDefaultRootElement(); - int offset = chng.getOffset(); - int length = chng.getLength(); - if (offset > 0) { - offset -= 1; - length += 1; - } - int index = lineMap.getElementIndex(offset); - Element rmCandidate = lineMap.getElement(index); - int rmOffs0 = rmCandidate.getStartOffset(); - int rmOffs1 = rmCandidate.getEndOffset(); - int lastOffset = rmOffs0; - try { - if (s == null) { - s = new Segment(); - } - getContent().getChars(offset, length, s); - boolean hasBreaks = false; - for (int i = 0; i < length; i++) { - char c = s.array[s.offset + i]; - //handle both newline and carriage return - if (c == '\r') { - char nextChar; - if (s.array.length > s.offset + i + 1 && i + 1 < length && (nextChar = s.array[s.offset + i + 1]) == '\n'){ - //check for \r\n - int breakOffset = offset + i + 2; - added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset)); - lastOffset = breakOffset; - hasBreaks = true; - i++; - }else{ - int breakOffset = offset + i + 1; - added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset)); - lastOffset = breakOffset; - hasBreaks = true; - } - - }else if (c == '\n'){ - int breakOffset = offset + i + 1; - added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset)); - lastOffset = breakOffset; - hasBreaks = true; - } - } - if (hasBreaks) { - int rmCount = 1; - removed.addElement(rmCandidate); - if ((offset + length == rmOffs1) && (lastOffset != rmOffs1) && ((index + 1) < lineMap.getElementCount())) { - rmCount += 1; - Element e = lineMap.getElement(index + 1); - removed.addElement(e); - rmOffs1 = e.getEndOffset(); - } - if (lastOffset < rmOffs1) { - added.addElement(createLeafElement(lineMap, null, lastOffset, rmOffs1)); - } - - Element[] aelems = new Element[added.size()]; - added.copyInto(aelems); - Element[] relems = new Element[removed.size()]; - removed.copyInto(relems); - ElementEdit ee = new ElementEdit(lineMap, index, relems, aelems); - chng.addEdit(ee); - lineMap.replace(index, relems.length, aelems); - } - if (isComposedTextAttributeDefined(attr)) { - insertComposedTextUpdate(chng, attr); - } - } catch (BadLocationException e) { - throw new Error("Internal error: " + e.toString()); - } - super.insertUpdate(chng, attr); - } - - static boolean isComposedTextAttributeDefined(AttributeSet as) { - return ((as != null) && (as.isDefined(StyleConstants.ComposedTextAttribute))); - } - - /** - * Updates any document structure as a result of text removal. This will - * happen within a write lock. Since the structure represents a line map, - * this just checks to see if the removal spans lines. If it does, the two - * lines outside of the removal area are joined together. - * - * @param chng - * the change event describing the edit - */ - protected void removeUpdate(DefaultDocumentEvent chng) { - removed.removeAllElements(); - BranchElement map = (BranchElement) getDefaultRootElement(); - int offset = chng.getOffset(); - int length = chng.getLength(); - int line0 = map.getElementIndex(offset); - int line1 = map.getElementIndex(offset + length); - if (line0 != line1) { - // a line was removed - for (int i = line0; i <= line1; i++) { - removed.addElement(map.getElement(i)); - } - int p0 = map.getElement(line0).getStartOffset(); - int p1 = map.getElement(line1).getEndOffset(); - Element[] aelems = new Element[1]; - aelems[0] = createLeafElement(map, null, p0, p1); - Element[] relems = new Element[removed.size()]; - removed.copyInto(relems); - ElementEdit ee = new ElementEdit(map, line0, relems, aelems); - chng.addEdit(ee); - map.replace(line0, relems.length, aelems); - } else { - // Check for the composed text element - Element line = map.getElement(line0); - if (!line.isLeaf()) { - Element leaf = line.getElement(line.getElementIndex(offset)); - if (isComposedTextElement(leaf)) { - Element[] aelem = new Element[1]; - aelem[0] = createLeafElement(map, null, line.getStartOffset(), line.getEndOffset()); - Element[] relem = new Element[1]; - relem[0] = line; - ElementEdit ee = new ElementEdit(map, line0, relem, aelem); - chng.addEdit(ee); - map.replace(line0, 1, aelem); - } - } - } - - super.removeUpdate(chng); - } - - private boolean isComposedTextElement(Element elem) { - AttributeSet as = elem.getAttributes(); - return isComposedTextAttributeDefined(as); - } - - // - // Inserts the composed text of an input method. The line element - // where the composed text is inserted into becomes an branch element - // which contains leaf elements of the composed text and the text - // backing store. - // - private void insertComposedTextUpdate(DefaultDocumentEvent chng, AttributeSet attr) { - added.removeAllElements(); - BranchElement lineMap = (BranchElement) getDefaultRootElement(); - int offset = chng.getOffset(); - int length = chng.getLength(); - int index = lineMap.getElementIndex(offset); - Element elem = lineMap.getElement(index); - int elemStart = elem.getStartOffset(); - int elemEnd = elem.getEndOffset(); - BranchElement[] abelem = new BranchElement[1]; - abelem[0] = (BranchElement) createBranchElement(lineMap, null); - Element[] relem = new Element[1]; - relem[0] = elem; - if (elemStart != offset) - added.addElement(createLeafElement(abelem[0], null, elemStart, offset)); - added.addElement(createLeafElement(abelem[0], attr, offset, offset + length)); - if (elemEnd != offset + length) - added.addElement(createLeafElement(abelem[0], null, offset + length, elemEnd)); - Element[] alelem = new Element[added.size()]; - added.copyInto(alelem); - ElementEdit ee = new ElementEdit(lineMap, index, relem, abelem); - chng.addEdit(ee); - - abelem[0].replace(0, 0, alelem); - lineMap.replace(index, 1, abelem); - } - - private AbstractElement defaultRoot; - private Vector added = new Vector(); // Vector - private Vector removed = new Vector(); // Vector - private transient Segment s; -} diff --git a/client/src/main/java/org/syntax/jedit/SyntaxDocument.java b/client/src/main/java/org/syntax/jedit/SyntaxDocument.java index 4dd3d016fd..86afbfd7e3 100644 --- a/client/src/main/java/org/syntax/jedit/SyntaxDocument.java +++ b/client/src/main/java/org/syntax/jedit/SyntaxDocument.java @@ -17,6 +17,8 @@ import org.syntax.jedit.tokenmarker.TokenMarker; +import com.mirth.connect.client.ui.components.MirthPlainDocument; + /** * A document implementation that can be tokenized by the syntax highlighting * system. diff --git a/client/src/test/java/com/mirth/connect/client/ui/components/MirthPlainDocumentTest.java b/client/src/test/java/com/mirth/connect/client/ui/components/MirthPlainDocumentTest.java index 9e9d945781..b7fca87392 100644 --- a/client/src/test/java/com/mirth/connect/client/ui/components/MirthPlainDocumentTest.java +++ b/client/src/test/java/com/mirth/connect/client/ui/components/MirthPlainDocumentTest.java @@ -23,7 +23,6 @@ import javax.swing.undo.UndoManager; import org.junit.Test; -import org.syntax.jedit.MirthPlainDocument; public class MirthPlainDocumentTest {