Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed markdown preview not rendering nested list structure (multi-level indented lists now display with proper nesting)
- Fixed "Continue last" and "Resume specific" staying enabled (and selected) when a directory or profile with no sessions is chosen in the session selector
- Fixed model list showing garbled names (e.g. "Mtoket 4.6 with 1M context") when the /model menu includes 1M-context variants and cost annotations

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbclaudecodegui</groupId>
<artifactId>netbeans-claude-code-gui</artifactId>
<version>0.22.48-SNAPSHOT</version>
<version>0.22.49-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Plugin Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,31 +299,9 @@ && isSeparatorRow(lines[i + 1])) {
continue;
}

// --- Unordered list ---
if (isUnordered(line)) {
html.append("<ul>");
while (i < lines.length && isUnordered(lines[i])) {
html.append("<li>")
.append(inlineToHtml(lines[i]
.replaceFirst("^\\s*[-*]\\s+", "")))
.append("</li>");
i++;
}
html.append("</ul>");
continue;
}

// --- Ordered list ---
if (isOrdered(line)) {
html.append("<ol>");
while (i < lines.length && isOrdered(lines[i])) {
html.append("<li>")
.append(inlineToHtml(lines[i]
.replaceFirst("^\\s*\\d+\\.\\s+", "")))
.append("</li>");
i++;
}
html.append("</ol>");
// --- List (nested ordered/unordered) ---
if (isUnordered(line) || isOrdered(line)) {
i = renderNestedList(lines, i, html);
continue;
}

Expand Down Expand Up @@ -531,5 +509,69 @@ private static void appendHtml(JEditorPane pane, String html) {
}
}

private static int renderNestedList(String[] lines, int start, StringBuilder html) {
List<Integer> indentStack = new ArrayList<>();
List<Boolean> typeStack = new ArrayList<>();

int i = start;
while (i < lines.length) {
String line = lines[i];

if (line.isBlank()) {
int j = i + 1;
while (j < lines.length && lines[j].isBlank()) j++;
if (j < lines.length && (isOrdered(lines[j]) || isUnordered(lines[j]))) {
i = j;
continue;
}
break;
}

if (!isOrdered(line) && !isUnordered(line)) break;

int indent = leadingSpaces(line);
boolean ordered = isOrdered(line);

if (indentStack.isEmpty()) {
html.append(ordered ? "<ol>" : "<ul>");
indentStack.add(indent);
typeStack.add(ordered);
} else {
int topIndent = indentStack.get(indentStack.size() - 1);
if (indent > topIndent) {
html.append(ordered ? "<ol>" : "<ul>");
indentStack.add(indent);
typeStack.add(ordered);
} else if (indent < topIndent) {
while (indentStack.size() > 1 && indentStack.get(indentStack.size() - 1) > indent) {
html.append(typeStack.get(typeStack.size() - 1) ? "</ol>" : "</ul>");
indentStack.remove(indentStack.size() - 1);
typeStack.remove(typeStack.size() - 1);
}
}
if (!typeStack.isEmpty() && typeStack.get(typeStack.size() - 1) != ordered) {
html.append(typeStack.get(typeStack.size() - 1) ? "</ol>" : "</ul>");
typeStack.set(typeStack.size() - 1, ordered);
html.append(ordered ? "<ol>" : "<ul>");
}
}

String content = line.replaceFirst("^\\s*(?:\\d+\\.|[-*])\\s+", "");
html.append("<li>").append(inlineToHtml(content)).append("</li>");
i++;
}

for (int k = typeStack.size() - 1; k >= 0; k--) {
html.append(typeStack.get(k) ? "</ol>" : "</ul>");
}
return i;
}

private static int leadingSpaces(String line) {
int n = 0;
while (n < line.length() && line.charAt(n) == ' ') n++;
return n;
}

private MarkdownRenderer() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,49 @@ void testHeadingSlugStripNonAlphanumeric() {
assertTrue(html.contains("<a name=\"hello-world-2024\">"), "slug strips punctuation");
}

// -------------------------------------------------------------------------
// toHtml() — nested lists
// -------------------------------------------------------------------------

@Test
void testNestedOrderedList() {
String md = "1. first\n 1. sub-first\n 2. sub-second\n2. second";
String html = MarkdownRenderer.toHtml(md);
// outer ol must contain an inner ol
int outerOl = html.indexOf("<ol>");
int innerOl = html.indexOf("<ol>", outerOl + 1);
assertTrue(innerOl > outerOl, "inner <ol> must exist inside outer <ol>");
assertTrue(html.contains("<li>first</li>") || html.contains("<li>first"),
"outer first item");
assertTrue(html.contains("sub-first"), "sub-first item");
assertTrue(html.contains("sub-second"), "sub-second item");
assertTrue(html.contains("second"), "outer second item");
// must have at least 2 </ol> closing tags
long closeCount = html.chars().filter(c -> c == '<').count();
assertTrue(html.indexOf("</ol>", html.indexOf("</ol>") + 1) > 0,
"at least 2 </ol> tags expected");
}

@Test
void testNestedUnorderedList() {
String md = "- alpha\n - sub-alpha\n - sub-beta\n- beta";
String html = MarkdownRenderer.toHtml(md);
int outerUl = html.indexOf("<ul>");
int innerUl = html.indexOf("<ul>", outerUl + 1);
assertTrue(innerUl > outerUl, "inner <ul> must exist inside outer <ul>");
assertTrue(html.contains("alpha"), "outer alpha");
assertTrue(html.contains("sub-alpha"), "sub-alpha");
}

@Test
void testMixedNestedList() {
String md = "1. item\n - nested-ul\n2. item2";
String html = MarkdownRenderer.toHtml(md);
assertTrue(html.contains("<ol>"), "outer ol");
assertTrue(html.contains("<ul>"), "inner ul");
assertTrue(html.contains("nested-ul"), "nested item text");
}

// -------------------------------------------------------------------------
// resolveImagePaths()
// -------------------------------------------------------------------------
Expand Down
Loading