commit
c385b9429f
Binary file not shown.
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 56 KiB |
@ -1,18 +1,37 @@
|
|||||||
package org.jcnc.jnotepad.ui.module;
|
package org.jcnc.jnotepad.ui.module;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import org.fxmisc.richtext.CodeArea;
|
||||||
|
import org.fxmisc.richtext.GenericStyledArea;
|
||||||
import org.fxmisc.richtext.LineNumberFactory;
|
import org.fxmisc.richtext.LineNumberFactory;
|
||||||
import org.fxmisc.richtext.StyleClassedTextArea;
|
import org.fxmisc.richtext.model.Paragraph;
|
||||||
|
import org.fxmisc.richtext.model.StyleSpans;
|
||||||
|
import org.fxmisc.richtext.model.StyleSpansBuilder;
|
||||||
import org.jcnc.jnotepad.util.LogUtil;
|
import org.jcnc.jnotepad.util.LogUtil;
|
||||||
import org.jcnc.jnotepad.views.manager.BottomStatusBoxManager;
|
import org.jcnc.jnotepad.views.manager.BottomStatusBoxManager;
|
||||||
import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
|
import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
|
||||||
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
|
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
|
||||||
|
import org.reactfx.Subscription;
|
||||||
|
import org.reactfx.collection.ListModification;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 行号文本区域
|
* 行号文本区域
|
||||||
@ -21,8 +40,49 @@ import java.io.IOException;
|
|||||||
*
|
*
|
||||||
* @author luke
|
* @author luke
|
||||||
*/
|
*/
|
||||||
public class LineNumberTextArea extends StyleClassedTextArea {
|
public class LineNumberTextArea extends CodeArea {
|
||||||
|
|
||||||
|
private static final String[] KEYWORDS = new String[]{
|
||||||
|
"abstract", "assert", "boolean", "break", "byte",
|
||||||
|
"case", "catch", "char", "class", "const",
|
||||||
|
"continue", "default", "do", "double", "else",
|
||||||
|
"enum", "extends", "final", "finally", "float",
|
||||||
|
"for", "goto", "if", "implements", "import",
|
||||||
|
"instanceof", "int", "interface", "long", "native",
|
||||||
|
"new", "package", "private", "protected", "public",
|
||||||
|
"return", "short", "static", "strictfp", "super",
|
||||||
|
"switch", "synchronized", "this", "throw", "throws",
|
||||||
|
"transient", "try", "void", "volatile", "while"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义用于匹配关键字、括号、分号、字符串和注释的正则表达式模式
|
||||||
|
*/
|
||||||
|
private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", KEYWORDS) + ")\\b";
|
||||||
|
private static final String PAREN_PATTERN = "\\(|\\)";
|
||||||
|
private static final String BRACE_PATTERN = "\\{|\\}";
|
||||||
|
private static final String BRACKET_PATTERN = "\\[|\\]";
|
||||||
|
private static final String SEMICOLON_PATTERN = "\\;";
|
||||||
|
private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\"";
|
||||||
|
private static final String COMMENT_PATTERN =
|
||||||
|
// 用于整体文本处理(文本块)
|
||||||
|
"//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/"
|
||||||
|
// 用于可见段落处理(逐行)
|
||||||
|
+ "|" + "/\\*[^\\v]*" + "|" + "^\\h*\\*([^\\v]*|/)";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用正则表达式将关键字、括号、分号、字符串和注释的模式组合成一个复合模式
|
||||||
|
*/
|
||||||
|
private static final Pattern PATTERN = Pattern.compile(
|
||||||
|
"(?<KEYWORD>" + KEYWORD_PATTERN + ")"
|
||||||
|
+ "|(?<PAREN>" + PAREN_PATTERN + ")"
|
||||||
|
+ "|(?<BRACE>" + BRACE_PATTERN + ")"
|
||||||
|
+ "|(?<BRACKET>" + BRACKET_PATTERN + ")"
|
||||||
|
+ "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
|
||||||
|
+ "|(?<STRING>" + STRING_PATTERN + ")"
|
||||||
|
+ "|(?<COMMENT>" + COMMENT_PATTERN + ")"
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于记录日志的静态Logger对象
|
* 用于记录日志的静态Logger对象
|
||||||
@ -35,14 +95,153 @@ public class LineNumberTextArea extends StyleClassedTextArea {
|
|||||||
* 用于创建 LineNumberTextArea 对象
|
* 用于创建 LineNumberTextArea 对象
|
||||||
*/
|
*/
|
||||||
public LineNumberTextArea() {
|
public LineNumberTextArea() {
|
||||||
//上、右、下、左
|
// 上、右、下、左
|
||||||
setPadding(new Insets(8, 0, 0, 0));
|
this.setPadding(new Insets(8, 0, 0, 0));
|
||||||
setStyle("-fx-font-family: 'Courier New';");
|
|
||||||
// 设置 LineNumberTextArea 的样式,包括边框和背景颜色
|
|
||||||
getStyleClass().add("line-number-text-area");
|
|
||||||
this.setParagraphGraphicFactory(LineNumberFactory.get(this));
|
|
||||||
initListeners();
|
|
||||||
|
|
||||||
|
// 在区域左侧添加行号
|
||||||
|
this.setParagraphGraphicFactory(LineNumberFactory.get(this));
|
||||||
|
this.setContextMenu(new DefaultContextMenu());
|
||||||
|
|
||||||
|
/*
|
||||||
|
重新计算所有文本的语法高亮,用户停止编辑区域后的500毫秒内
|
||||||
|
*/
|
||||||
|
Subscription cleanupWhenNoLongerNeedIt = this
|
||||||
|
.multiPlainChanges()
|
||||||
|
.successionEnds(Duration.ofMillis(500))
|
||||||
|
.subscribe(ignore -> this.setStyleSpans(0, computeHighlighting(this.getText())));
|
||||||
|
|
||||||
|
this.getVisibleParagraphs().addModificationObserver
|
||||||
|
(
|
||||||
|
new LineNumberTextArea.VisibleParagraphStyler<>(this, this::computeHighlighting)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 自动缩进:在按下回车键时插入上一行的缩进
|
||||||
|
final Pattern whiteSpace = Pattern.compile("^\\s+");
|
||||||
|
this.addEventHandler(KeyEvent.KEY_PRESSED, kE ->
|
||||||
|
{
|
||||||
|
if (kE.getCode() == KeyCode.ENTER) {
|
||||||
|
int caretPosition = this.getCaretPosition();
|
||||||
|
int currentParagraph = this.getCurrentParagraph();
|
||||||
|
Matcher m0 = whiteSpace.matcher(this.getParagraph(currentParagraph - 1).getSegments().get(0));
|
||||||
|
if (m0.find()) {
|
||||||
|
Platform.runLater(() -> this.insertText(caretPosition, m0.group()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initListeners();
|
||||||
|
this.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/css/java_code_styles.css")).toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private StyleSpans<Collection<String>> computeHighlighting(String text) {
|
||||||
|
Matcher matcher = PATTERN.matcher(text);
|
||||||
|
int lastKwEnd = 0;
|
||||||
|
StyleSpansBuilder<Collection<String>> spansBuilder
|
||||||
|
= new StyleSpansBuilder<>();
|
||||||
|
while (matcher.find()) {
|
||||||
|
String styleClass = getStyleClass(matcher);
|
||||||
|
spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
|
||||||
|
spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start());
|
||||||
|
lastKwEnd = matcher.end();
|
||||||
|
}
|
||||||
|
spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
|
||||||
|
return spansBuilder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getStyleClass(Matcher matcher) {
|
||||||
|
String styleClass =
|
||||||
|
matcher.group("KEYWORD") != null ? "keyword" :
|
||||||
|
matcher.group("PAREN") != null ? "paren" :
|
||||||
|
matcher.group("BRACE") != null ? "brace" :
|
||||||
|
matcher.group("BRACKET") != null ? "bracket" :
|
||||||
|
matcher.group("SEMICOLON") != null ? "semicolon" :
|
||||||
|
matcher.group("STRING") != null ? "string" :
|
||||||
|
matcher.group("COMMENT") != null ? "comment" :
|
||||||
|
null; /* 永远不会发生 */
|
||||||
|
assert styleClass != null;
|
||||||
|
return styleClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class VisibleParagraphStyler<PS, SEG, S> implements Consumer<ListModification<? extends Paragraph<PS, SEG, S>>> {
|
||||||
|
private final GenericStyledArea<PS, SEG, S> area;
|
||||||
|
private final Function<String,StyleSpans<S>> computeStyles;
|
||||||
|
private int prevParagraph, prevTextLength;
|
||||||
|
|
||||||
|
public VisibleParagraphStyler( GenericStyledArea<PS, SEG, S> area, Function<String,StyleSpans<S>> computeStyles )
|
||||||
|
{
|
||||||
|
this.computeStyles = computeStyles;
|
||||||
|
this.area = area;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept( ListModification<? extends Paragraph<PS, SEG, S>> lm )
|
||||||
|
{
|
||||||
|
if (lm.getAddedSize() > 0) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
int paragraph = Math.min(area.firstVisibleParToAllParIndex() + lm.getFrom(), area.getParagraphs().size() - 1);
|
||||||
|
String text = area.getText(paragraph, 0, paragraph, area.getParagraphLength(paragraph));
|
||||||
|
|
||||||
|
if (paragraph != prevParagraph || text.length() != prevTextLength) {
|
||||||
|
if (paragraph < area.getParagraphs().size() - 1) {
|
||||||
|
int startPos = area.getAbsolutePosition(paragraph, 0);
|
||||||
|
area.setStyleSpans(startPos, computeStyles.apply(text));
|
||||||
|
}
|
||||||
|
prevTextLength = text.length();
|
||||||
|
prevParagraph = paragraph;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultContextMenu extends ContextMenu {
|
||||||
|
private final MenuItem fold;
|
||||||
|
private final MenuItem unfold;
|
||||||
|
private final MenuItem print;
|
||||||
|
|
||||||
|
public DefaultContextMenu() {
|
||||||
|
fold = new MenuItem("折叠所选文本");
|
||||||
|
fold.setOnAction(aE -> {
|
||||||
|
hide();
|
||||||
|
fold();
|
||||||
|
});
|
||||||
|
|
||||||
|
unfold = new MenuItem("从光标处展开");
|
||||||
|
unfold.setOnAction(aE -> {
|
||||||
|
hide();
|
||||||
|
unfold();
|
||||||
|
});
|
||||||
|
|
||||||
|
print = new MenuItem("打印");
|
||||||
|
print.setOnAction(aE -> {
|
||||||
|
hide();
|
||||||
|
print();
|
||||||
|
});
|
||||||
|
|
||||||
|
getItems().addAll(fold, unfold, print);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 折叠多行所选文本,仅显示第一行并隐藏其余部分。
|
||||||
|
*/
|
||||||
|
private void fold() {
|
||||||
|
((CodeArea) getOwnerNode()).foldSelectedParagraphs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展开当前行/段落(如果有折叠)。
|
||||||
|
*/
|
||||||
|
private void unfold() {
|
||||||
|
CodeArea area = (CodeArea) getOwnerNode();
|
||||||
|
area.unfoldParagraphs(area.getCurrentParagraph());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void print() {
|
||||||
|
System.out.println(((CodeArea) getOwnerNode()).getText());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,8 +286,8 @@ public class LineNumberTextArea extends StyleClassedTextArea {
|
|||||||
// 记录保存操作的日志信息
|
// 记录保存操作的日志信息
|
||||||
LogUtil.getLogger(this.getClass()).info("正在自动保存---");
|
LogUtil.getLogger(this.getClass()).info("正在自动保存---");
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
// 如果发生IO异常,记录忽略的日志信息,但不中断程序执行
|
// 如果发生IO异常,记录忽视的日志信息,但不中断程序执行
|
||||||
LogUtil.getLogger(this.getClass()).info("已忽视IO异常!");
|
LogUtil.getLogger(this.getClass()).info("已忽视IO异常!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/main/resources/css/java_code_styles.css
Normal file
40
src/main/resources/css/java_code_styles.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/* CSS样式注释 */
|
||||||
|
|
||||||
|
/* 标记关键字 */
|
||||||
|
.keyword {
|
||||||
|
-fx-fill: purple; /* 设置文本颜色为紫色 */
|
||||||
|
-fx-font-weight: bold; /* 设置文本加粗 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标记分号 */
|
||||||
|
.semicolon {
|
||||||
|
-fx-font-weight: bold; /* 设置文本加粗 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标记括号 */
|
||||||
|
.paren {
|
||||||
|
-fx-fill: firebrick; /* 设置文本颜色为火砖红 */
|
||||||
|
-fx-font-weight: bold; /* 设置文本加粗 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标记方括号 */
|
||||||
|
.bracket {
|
||||||
|
-fx-fill: darkgreen; /* 设置文本颜色为深绿色 */
|
||||||
|
-fx-font-weight: bold; /* 设置文本加粗 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标记大括号 */
|
||||||
|
.brace {
|
||||||
|
-fx-fill: teal; /* 设置文本颜色为青色 */
|
||||||
|
-fx-font-weight: bold; /* 设置文本加粗 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标记字符串 */
|
||||||
|
.string {
|
||||||
|
-fx-fill: blue; /* 设置文本颜色为蓝色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标记注释 */
|
||||||
|
.comment {
|
||||||
|
-fx-fill: cadetblue; /* 设置文本颜色为军校蓝 */
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user