added MD support
This commit is contained in:
parent
a4dedd1324
commit
de1ae57da7
@ -15,7 +15,7 @@ import java.io.InputStreamReader;
|
|||||||
*/
|
*/
|
||||||
public class MainApp {
|
public class MainApp {
|
||||||
|
|
||||||
public static final String APP_VERSION = "1.4.6";
|
public static final String APP_VERSION = "1.4.7";
|
||||||
|
|
||||||
public enum OS {
|
public enum OS {
|
||||||
WINDOWS, LINUX, MACOS, UNKNOWN
|
WINDOWS, LINUX, MACOS, UNKNOWN
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import cz.kamma.kfmanager.config.AppConfig;
|
|||||||
import cz.kamma.kfmanager.model.FileItem;
|
import cz.kamma.kfmanager.model.FileItem;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.text.html.HTMLEditorKit;
|
||||||
|
import javax.swing.text.html.StyleSheet;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.InputEvent;
|
import java.awt.event.InputEvent;
|
||||||
@ -20,6 +22,7 @@ import java.util.regex.Matcher;
|
|||||||
*/
|
*/
|
||||||
public class FileEditor extends JFrame {
|
public class FileEditor extends JFrame {
|
||||||
private JTextArea textArea;
|
private JTextArea textArea;
|
||||||
|
private JEditorPane markdownPane;
|
||||||
private JScrollPane scrollPane;
|
private JScrollPane scrollPane;
|
||||||
private File file;
|
private File file;
|
||||||
private String virtualPath;
|
private String virtualPath;
|
||||||
@ -55,6 +58,8 @@ public class FileEditor extends JFrame {
|
|||||||
private JButton prevPageBtn = null;
|
private JButton prevPageBtn = null;
|
||||||
private JButton nextPageBtn = null;
|
private JButton nextPageBtn = null;
|
||||||
private JLabel pageOffsetLabel = null;
|
private JLabel pageOffsetLabel = null;
|
||||||
|
private boolean markdownMode = false;
|
||||||
|
private JCheckBoxMenuItem markdownItem = null;
|
||||||
private javax.swing.undo.UndoManager undoManager;
|
private javax.swing.undo.UndoManager undoManager;
|
||||||
|
|
||||||
// Search support
|
// Search support
|
||||||
@ -171,6 +176,9 @@ public class FileEditor extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showSearchPanel(boolean focusField) {
|
private void showSearchPanel(boolean focusField) {
|
||||||
|
if (markdownMode) {
|
||||||
|
setMarkdownMode(false);
|
||||||
|
}
|
||||||
searchPanel.setVisible(true);
|
searchPanel.setVisible(true);
|
||||||
wholeWordCheckBox.setSelected(lastWholeWord);
|
wholeWordCheckBox.setSelected(lastWholeWord);
|
||||||
caseSensitiveCheckBox.setSelected(lastCaseSensitive);
|
caseSensitiveCheckBox.setSelected(lastCaseSensitive);
|
||||||
@ -210,7 +218,7 @@ public class FileEditor extends JFrame {
|
|||||||
|
|
||||||
private void hideSearchPanel() {
|
private void hideSearchPanel() {
|
||||||
searchPanel.setVisible(false);
|
searchPanel.setVisible(false);
|
||||||
textArea.requestFocusInWindow();
|
getActiveTextComponent().requestFocusInWindow();
|
||||||
revalidate();
|
revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,6 +420,17 @@ public class FileEditor extends JFrame {
|
|||||||
textArea.setColumns(120);
|
textArea.setColumns(120);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markdownPane = new JEditorPane();
|
||||||
|
markdownPane.setEditable(false);
|
||||||
|
markdownPane.setContentType("text/html");
|
||||||
|
markdownPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
|
||||||
|
markdownPane.addHyperlinkListener(e -> {
|
||||||
|
if (e.getEventType() == javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) {
|
||||||
|
openMarkdownLink(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
markdownPane.addCaretListener(e -> updateStatus());
|
||||||
|
|
||||||
// Menu bar
|
// Menu bar
|
||||||
createMenuBar();
|
createMenuBar();
|
||||||
|
|
||||||
@ -512,6 +531,15 @@ public class FileEditor extends JFrame {
|
|||||||
});
|
});
|
||||||
|
|
||||||
textArea.setComponentPopupMenu(popup);
|
textArea.setComponentPopupMenu(popup);
|
||||||
|
|
||||||
|
JPopupMenu markdownPopup = new JPopupMenu();
|
||||||
|
JMenuItem markdownCopyItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CopyAction());
|
||||||
|
markdownCopyItem.setText("Copy");
|
||||||
|
markdownPopup.add(markdownCopyItem);
|
||||||
|
JMenuItem markdownSelectAllItem = new JMenuItem("Select All");
|
||||||
|
markdownSelectAllItem.addActionListener(e -> markdownPane.selectAll());
|
||||||
|
markdownPopup.add(markdownSelectAllItem);
|
||||||
|
markdownPane.setComponentPopupMenu(markdownPopup);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyAppearance() {
|
private void applyAppearance() {
|
||||||
@ -540,6 +568,14 @@ public class FileEditor extends JFrame {
|
|||||||
if (readOnly) {
|
if (readOnly) {
|
||||||
textArea.getCaret().setVisible(true);
|
textArea.getCaret().setVisible(true);
|
||||||
}
|
}
|
||||||
|
if (markdownPane != null) {
|
||||||
|
markdownPane.setFont(textArea.getFont());
|
||||||
|
markdownPane.setBackground(textArea.getBackground());
|
||||||
|
markdownPane.setForeground(textArea.getForeground());
|
||||||
|
if (markdownMode) {
|
||||||
|
renderMarkdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyRecursiveColors(Container container, Color bg, boolean dark) {
|
private void applyRecursiveColors(Container container, Color bg, boolean dark) {
|
||||||
@ -696,6 +732,12 @@ public class FileEditor extends JFrame {
|
|||||||
viewMenu.add(wrapItem);
|
viewMenu.add(wrapItem);
|
||||||
viewMenu.addSeparator();
|
viewMenu.addSeparator();
|
||||||
|
|
||||||
|
markdownItem = new JCheckBoxMenuItem("Markdown preview");
|
||||||
|
markdownItem.setState(markdownMode);
|
||||||
|
markdownItem.setEnabled(readOnly && isMarkdownFile());
|
||||||
|
markdownItem.addActionListener(e -> setMarkdownMode(markdownItem.getState()));
|
||||||
|
viewMenu.add(markdownItem);
|
||||||
|
|
||||||
JCheckBoxMenuItem hexItem = new JCheckBoxMenuItem("Hex view");
|
JCheckBoxMenuItem hexItem = new JCheckBoxMenuItem("Hex view");
|
||||||
hexItem.setState(hexMode);
|
hexItem.setState(hexMode);
|
||||||
hexItem.addActionListener(e -> {
|
hexItem.addActionListener(e -> {
|
||||||
@ -706,6 +748,262 @@ public class FileEditor extends JFrame {
|
|||||||
menuBar.add(viewMenu);
|
menuBar.add(viewMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private javax.swing.text.JTextComponent getActiveTextComponent() {
|
||||||
|
return markdownMode ? markdownPane : textArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMarkdownFile() {
|
||||||
|
String name = virtualPath != null ? virtualPath : file.getName();
|
||||||
|
name = name.toLowerCase();
|
||||||
|
return name.endsWith(".md") || name.endsWith(".markdown") || name.endsWith(".mdown") || name.endsWith(".mkd");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMarkdownMode(boolean on) {
|
||||||
|
if (on && (!readOnly || !isMarkdownFile() || hexMode || isImageFile(file))) {
|
||||||
|
on = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
markdownMode = on;
|
||||||
|
if (markdownItem != null) {
|
||||||
|
markdownItem.setState(on);
|
||||||
|
markdownItem.setEnabled(readOnly && isMarkdownFile() && !hexMode && !isImageFile(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (on) {
|
||||||
|
renderMarkdown();
|
||||||
|
scrollPane.setViewportView(markdownPane);
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
markdownPane.setCaretPosition(0);
|
||||||
|
markdownPane.requestFocusInWindow();
|
||||||
|
});
|
||||||
|
} else if (scrollPane != null && scrollPane.getViewport().getView() == markdownPane) {
|
||||||
|
scrollPane.setViewportView(textArea);
|
||||||
|
SwingUtilities.invokeLater(() -> textArea.requestFocusInWindow());
|
||||||
|
}
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderMarkdown() {
|
||||||
|
String raw = textArea.getText();
|
||||||
|
markdownPane.setEditorKit(createMarkdownEditorKit());
|
||||||
|
markdownPane.setText(markdownToHtml(raw));
|
||||||
|
markdownPane.setCaretPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openMarkdownLink(javax.swing.event.HyperlinkEvent event) {
|
||||||
|
try {
|
||||||
|
if (event.getURL() != null) {
|
||||||
|
Desktop.getDesktop().browse(event.getURL().toURI());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String description = event.getDescription();
|
||||||
|
if (description == null || description.isBlank()) return;
|
||||||
|
java.net.URI uri = new java.net.URI(description);
|
||||||
|
if (uri.isAbsolute()) {
|
||||||
|
Desktop.getDesktop().browse(uri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File baseDir = file.getParentFile();
|
||||||
|
if (baseDir != null) {
|
||||||
|
Desktop.getDesktop().open(new File(baseDir, description));
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Cannot open link:\n" + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HTMLEditorKit createMarkdownEditorKit() {
|
||||||
|
HTMLEditorKit kit = new HTMLEditorKit();
|
||||||
|
StyleSheet styles = kit.getStyleSheet();
|
||||||
|
Font font = config.getEditorFont();
|
||||||
|
Color bg = textArea.getBackground();
|
||||||
|
Color fg = textArea.getForeground();
|
||||||
|
String fontFamily = font != null ? font.getFamily() : "SansSerif";
|
||||||
|
int fontSize = font != null ? font.getSize() : 13;
|
||||||
|
styles.addRule("body { font-family: " + cssString(fontFamily) + "; font-size: " + fontSize + "pt; "
|
||||||
|
+ "background: " + toCssColor(bg) + "; color: " + toCssColor(fg) + "; margin: 12px; }");
|
||||||
|
styles.addRule("h1 { font-size: 190%; margin: 0 0 10px 0; }");
|
||||||
|
styles.addRule("h2 { font-size: 155%; margin: 18px 0 8px 0; }");
|
||||||
|
styles.addRule("h3 { font-size: 130%; margin: 16px 0 7px 0; }");
|
||||||
|
styles.addRule("h4, h5, h6 { margin: 14px 0 6px 0; }");
|
||||||
|
styles.addRule("p { margin: 0 0 10px 0; }");
|
||||||
|
styles.addRule("ul, ol { margin-top: 0; margin-bottom: 10px; }");
|
||||||
|
styles.addRule("blockquote { margin: 0 0 10px 12px; padding-left: 10px; border-left: 3px solid #808080; }");
|
||||||
|
styles.addRule("pre { font-family: Monospaced; font-size: " + fontSize + "pt; margin: 0 0 10px 0; padding: 8px; }");
|
||||||
|
styles.addRule("code { font-family: Monospaced; }");
|
||||||
|
styles.addRule("a { color: #2f7ed8; }");
|
||||||
|
return kit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String markdownToHtml(String markdown) {
|
||||||
|
StringBuilder body = new StringBuilder();
|
||||||
|
String[] lines = markdown.replace("\r\n", "\n").replace('\r', '\n').split("\n", -1);
|
||||||
|
StringBuilder paragraph = new StringBuilder();
|
||||||
|
StringBuilder list = null;
|
||||||
|
String listTag = null;
|
||||||
|
boolean inFence = false;
|
||||||
|
StringBuilder fence = new StringBuilder();
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (trimmed.startsWith("```")) {
|
||||||
|
if (inFence) {
|
||||||
|
body.append("<pre><code>").append(escapeHtml(fence.toString())).append("</code></pre>");
|
||||||
|
fence.setLength(0);
|
||||||
|
inFence = false;
|
||||||
|
} else {
|
||||||
|
flushParagraph(body, paragraph);
|
||||||
|
list = flushList(body, list, listTag);
|
||||||
|
listTag = null;
|
||||||
|
inFence = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inFence) {
|
||||||
|
fence.append(line).append('\n');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
flushParagraph(body, paragraph);
|
||||||
|
list = flushList(body, list, listTag);
|
||||||
|
listTag = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String heading = headingHtml(trimmed);
|
||||||
|
if (heading != null) {
|
||||||
|
flushParagraph(body, paragraph);
|
||||||
|
list = flushList(body, list, listTag);
|
||||||
|
listTag = null;
|
||||||
|
body.append(heading);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.matches("[-*_]{3,}")) {
|
||||||
|
flushParagraph(body, paragraph);
|
||||||
|
list = flushList(body, list, listTag);
|
||||||
|
listTag = null;
|
||||||
|
body.append("<hr>");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher unordered = Pattern.compile("^[-*+]\\s+(.+)$").matcher(trimmed);
|
||||||
|
Matcher ordered = Pattern.compile("^\\d+[.)]\\s+(.+)$").matcher(trimmed);
|
||||||
|
if (unordered.matches() || ordered.matches()) {
|
||||||
|
flushParagraph(body, paragraph);
|
||||||
|
String currentTag = unordered.matches() ? "ul" : "ol";
|
||||||
|
if (list != null && !currentTag.equals(listTag)) {
|
||||||
|
list = flushList(body, list, listTag);
|
||||||
|
}
|
||||||
|
if (list == null) {
|
||||||
|
list = new StringBuilder();
|
||||||
|
listTag = currentTag;
|
||||||
|
}
|
||||||
|
String item = unordered.matches() ? unordered.group(1) : ordered.group(1);
|
||||||
|
list.append("<li>").append(renderInline(item)).append("</li>");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.startsWith(">")) {
|
||||||
|
flushParagraph(body, paragraph);
|
||||||
|
list = flushList(body, list, listTag);
|
||||||
|
listTag = null;
|
||||||
|
String quote = trimmed.replaceFirst("^>\\s?", "");
|
||||||
|
body.append("<blockquote>").append(renderInline(quote)).append("</blockquote>");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paragraph.length() > 0) {
|
||||||
|
paragraph.append(' ');
|
||||||
|
}
|
||||||
|
paragraph.append(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inFence) {
|
||||||
|
body.append("<pre><code>").append(escapeHtml(fence.toString())).append("</code></pre>");
|
||||||
|
}
|
||||||
|
flushParagraph(body, paragraph);
|
||||||
|
flushList(body, list, listTag);
|
||||||
|
return "<html><body>" + body + "</body></html>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushParagraph(StringBuilder body, StringBuilder paragraph) {
|
||||||
|
if (paragraph.length() == 0) return;
|
||||||
|
body.append("<p>").append(renderInline(paragraph.toString())).append("</p>");
|
||||||
|
paragraph.setLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder flushList(StringBuilder body, StringBuilder list, String listTag) {
|
||||||
|
if (list == null || listTag == null) return null;
|
||||||
|
body.append('<').append(listTag).append('>').append(list).append("</").append(listTag).append('>');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String headingHtml(String trimmed) {
|
||||||
|
Matcher m = Pattern.compile("^(#{1,6})\\s+(.+)$").matcher(trimmed);
|
||||||
|
if (!m.matches()) return null;
|
||||||
|
int level = m.group(1).length();
|
||||||
|
return "<h" + level + ">" + renderInline(m.group(2)) + "</h" + level + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String renderInline(String text) {
|
||||||
|
java.util.List<String> codeSpans = new java.util.ArrayList<>();
|
||||||
|
Matcher codeMatcher = Pattern.compile("`([^`]+)`").matcher(text);
|
||||||
|
StringBuffer protectedText = new StringBuffer();
|
||||||
|
while (codeMatcher.find()) {
|
||||||
|
String token = "{{KF_MD_CODE_" + codeSpans.size() + "}}";
|
||||||
|
codeSpans.add("<code>" + escapeHtml(codeMatcher.group(1)) + "</code>");
|
||||||
|
codeMatcher.appendReplacement(protectedText, Matcher.quoteReplacement(token));
|
||||||
|
}
|
||||||
|
codeMatcher.appendTail(protectedText);
|
||||||
|
|
||||||
|
String html = escapeHtml(protectedText.toString());
|
||||||
|
html = html.replaceAll("\\*\\*([^*]+)\\*\\*", "<strong>$1</strong>");
|
||||||
|
html = html.replaceAll("__([^_]+)__", "<strong>$1</strong>");
|
||||||
|
html = html.replaceAll("(?<!\\*)\\*([^*]+)\\*(?!\\*)", "<em>$1</em>");
|
||||||
|
html = html.replaceAll("(?<!_)_([^_]+)_(?!_)", "<em>$1</em>");
|
||||||
|
html = replaceLinks(html);
|
||||||
|
|
||||||
|
for (int i = 0; i < codeSpans.size(); i++) {
|
||||||
|
html = html.replace("{{KF_MD_CODE_" + i + "}}", codeSpans.get(i));
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String replaceLinks(String html) {
|
||||||
|
Matcher m = Pattern.compile("\\[([^\\]]+)]\\(([^\\s)]+)\\)").matcher(html);
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
while (m.find()) {
|
||||||
|
String label = m.group(1);
|
||||||
|
String href = m.group(2);
|
||||||
|
String replacement = "<a href=\"" + escapeAttribute(href) + "\">" + label + "</a>";
|
||||||
|
m.appendReplacement(sb, Matcher.quoteReplacement(replacement));
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapeHtml(String text) {
|
||||||
|
return text.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("\"", """);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapeAttribute(String text) {
|
||||||
|
return escapeHtml(text).replace("'", "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toCssColor(Color color) {
|
||||||
|
if (color == null) return "#ffffff";
|
||||||
|
return "#%02x%02x%02x".formatted(color.getRed(), color.getGreen(), color.getBlue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cssString(String value) {
|
||||||
|
return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'";
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureHexControls() {
|
private void ensureHexControls() {
|
||||||
if (hexControlPanel != null) return;
|
if (hexControlPanel != null) return;
|
||||||
hexControlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
hexControlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||||
@ -746,7 +1044,7 @@ public class FileEditor extends JFrame {
|
|||||||
showSearchPanel(false);
|
showSearchPanel(false);
|
||||||
}
|
}
|
||||||
findNext();
|
findNext();
|
||||||
textArea.requestFocusInWindow();
|
getActiveTextComponent().requestFocusInWindow();
|
||||||
},
|
},
|
||||||
KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0),
|
KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0),
|
||||||
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
@ -821,14 +1119,20 @@ public class FileEditor extends JFrame {
|
|||||||
|
|
||||||
rootPane.registerKeyboardAction(e -> {
|
rootPane.registerKeyboardAction(e -> {
|
||||||
findPrevious();
|
findPrevious();
|
||||||
textArea.requestFocusInWindow();
|
getActiveTextComponent().requestFocusInWindow();
|
||||||
},
|
},
|
||||||
KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.SHIFT_DOWN_MASK),
|
KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.SHIFT_DOWN_MASK),
|
||||||
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setHexMode(boolean on) {
|
private void setHexMode(boolean on) {
|
||||||
|
if (on && markdownMode) {
|
||||||
|
setMarkdownMode(false);
|
||||||
|
}
|
||||||
this.hexMode = on;
|
this.hexMode = on;
|
||||||
|
if (markdownItem != null) {
|
||||||
|
markdownItem.setEnabled(readOnly && isMarkdownFile() && !on && !isImageFile(file));
|
||||||
|
}
|
||||||
if (on) {
|
if (on) {
|
||||||
// ensure bytes loaded
|
// ensure bytes loaded
|
||||||
try {
|
try {
|
||||||
@ -1009,7 +1313,7 @@ public class FileEditor extends JFrame {
|
|||||||
if (virtualPath != null && ftpProfile == null) {
|
if (virtualPath != null && ftpProfile == null) {
|
||||||
try {
|
try {
|
||||||
fileBytes = cz.kamma.kfmanager.service.FileOperations.readFileFromArchive(file, virtualPath);
|
fileBytes = cz.kamma.kfmanager.service.FileOperations.readFileFromArchive(file, virtualPath);
|
||||||
boolean binary = isBinary(fileBytes);
|
boolean binary = isBinaryForCurrentFile(fileBytes);
|
||||||
if (binary && readOnly) {
|
if (binary && readOnly) {
|
||||||
hexMode = true;
|
hexMode = true;
|
||||||
buildHexViewText(0L);
|
buildHexViewText(0L);
|
||||||
@ -1029,6 +1333,7 @@ public class FileEditor extends JFrame {
|
|||||||
}
|
}
|
||||||
if (undoManager != null) undoManager.discardAllEdits();
|
if (undoManager != null) undoManager.discardAllEdits();
|
||||||
modified = false;
|
modified = false;
|
||||||
|
updateMarkdownPreviewAfterLoad();
|
||||||
updateTitle();
|
updateTitle();
|
||||||
updateStatus();
|
updateStatus();
|
||||||
return;
|
return;
|
||||||
@ -1058,7 +1363,7 @@ public class FileEditor extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean binaryProbe = isBinary(probe);
|
boolean binaryProbe = isBinaryForCurrentFile(probe);
|
||||||
|
|
||||||
if (binaryProbe && readOnly && size > maxFullLoadBytes) {
|
if (binaryProbe && readOnly && size > maxFullLoadBytes) {
|
||||||
// Open RAF and stream pages
|
// Open RAF and stream pages
|
||||||
@ -1077,7 +1382,7 @@ public class FileEditor extends JFrame {
|
|||||||
} else {
|
} else {
|
||||||
// Small or text file: load fully
|
// Small or text file: load fully
|
||||||
fileBytes = readFileBytesWithChunking(file, size);
|
fileBytes = readFileBytesWithChunking(file, size);
|
||||||
boolean binary = isBinary(fileBytes);
|
boolean binary = isBinaryForCurrentFile(fileBytes);
|
||||||
if (binary && readOnly) {
|
if (binary && readOnly) {
|
||||||
hexMode = true;
|
hexMode = true;
|
||||||
buildHexViewText(0L);
|
buildHexViewText(0L);
|
||||||
@ -1098,6 +1403,7 @@ public class FileEditor extends JFrame {
|
|||||||
}
|
}
|
||||||
if (undoManager != null) undoManager.discardAllEdits();
|
if (undoManager != null) undoManager.discardAllEdits();
|
||||||
modified = false;
|
modified = false;
|
||||||
|
updateMarkdownPreviewAfterLoad();
|
||||||
updateTitle();
|
updateTitle();
|
||||||
updateStatus();
|
updateStatus();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -1105,6 +1411,26 @@ public class FileEditor extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateMarkdownPreviewAfterLoad() {
|
||||||
|
boolean shouldPreview = readOnly && isMarkdownFile() && !hexMode && !isImageFile(file);
|
||||||
|
setMarkdownMode(shouldPreview);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBinaryForCurrentFile(byte[] bytes) {
|
||||||
|
if (isMarkdownFile()) {
|
||||||
|
return containsNulByte(bytes);
|
||||||
|
}
|
||||||
|
return isBinary(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsNulByte(byte[] bytes) {
|
||||||
|
if (bytes == null) return false;
|
||||||
|
for (byte b : bytes) {
|
||||||
|
if (b == 0) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isBinary(byte[] bytes) {
|
private boolean isBinary(byte[] bytes) {
|
||||||
if (bytes == null || bytes.length == 0) return false;
|
if (bytes == null || bytes.length == 0) return false;
|
||||||
int nonPrintable = 0;
|
int nonPrintable = 0;
|
||||||
@ -1499,6 +1825,26 @@ public class FileEditor extends JFrame {
|
|||||||
statusSelLabel.setText(" ");
|
statusSelLabel.setText(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (markdownMode) {
|
||||||
|
String text = textArea.getText();
|
||||||
|
int chars = text != null ? text.length() : 0;
|
||||||
|
int bytes = 0;
|
||||||
|
try {
|
||||||
|
bytes = text != null ? text.getBytes("UTF-8").length : 0;
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
statusPosLabel.setText("Markdown preview | %d chars / %d bytes".formatted(chars, bytes));
|
||||||
|
|
||||||
|
String selected = markdownPane.getSelectedText();
|
||||||
|
int selChars = selected != null ? selected.length() : 0;
|
||||||
|
if (selChars > 0) {
|
||||||
|
int selBytes = 0;
|
||||||
|
try {
|
||||||
|
selBytes = selected.getBytes("UTF-8").length;
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
statusSelLabel.setText("Selected: %d chars / %d bytes".formatted(selChars, selBytes));
|
||||||
|
} else {
|
||||||
|
statusSelLabel.setText(" ");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int caret = textArea.getCaretPosition();
|
int caret = textArea.getCaretPosition();
|
||||||
String text = textArea.getText();
|
String text = textArea.getText();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user