line number, symtax highlight, initial dark theme

This commit is contained in:
2026-02-01 15:21:11 +08:00
parent 20e91b4b0a
commit 480d628260
8 changed files with 306 additions and 9 deletions

View File

@@ -13,5 +13,8 @@
<entry name="editorFont" type="Font" key="editor-font">
<default code="true">QFontDatabase::systemFont(QFontDatabase::FixedFont)</default>
</entry>
<entry name="editorDarkTheme" type="Bool" key="editor-dark-theme">
<default>false</default>
</entry>
</group>
</kcfg>

View File

@@ -44,6 +44,20 @@
<item row="1" column="1">
<widget class="KFontRequester" name="kcfg_editorFont"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>eDITOR dARK tHEME</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="kcfg_editorDarkTheme">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@@ -109,7 +109,9 @@ void MainWindow::setupActions()
lexerNoneAction->setText("None (Normal Text)");
actionCollection()->addAction(u"lexer_none"_s, lexerNoneAction);
connect(lexerNoneAction, &QAction::triggered, this, [this](){
// m_editor->setLexer(nullptr);
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->setLexer(nullptr);
}
});
for (const QChar & group : LexerGroupActionMenu::groups()) {
@@ -167,6 +169,17 @@ void MainWindow::setupActions()
// m_editor->setIndentationGuides(!m_editor->indentationGuides());
});
QAction *toggleEditorDarkThemeAction = new QAction(this);
toggleEditorDarkThemeAction->setText("Editor Dark Theme");
toggleEditorDarkThemeAction->setCheckable(true);
toggleEditorDarkThemeAction->setChecked(AppSettings::editorDarkTheme());
actionCollection()->addAction(u"toggle_editor_dark_theme"_s, toggleEditorDarkThemeAction);
connect(toggleEditorDarkThemeAction, &QAction::toggled, this, [this](bool checked){
AppSettings::setEditorDarkTheme(checked);
AppSettings::self()->save();
applySettingsToAllEditors();
});
// Load themes
KColorSchemeManager *manager = KColorSchemeManager::instance();
auto *colorSelectionMenu = KColorSchemeMenu::createMenu(manager, this);
@@ -185,8 +198,12 @@ void MainWindow::showSettings()
dialog->setFaceType(KPageDialog::FlatList);
dialog->addPage(new SettingsPage<Ui::GeneralSettings>(dialog), "Appearance", "preferences-desktop-theme-global");
connect(dialog, &KConfigDialog::settingsChanged, this, [](const QString &dialogName){
qDebug() << dialogName << "changed";
connect(dialog, &KConfigDialog::settingsChanged, this, [this](const QString &dialogName){
Q_UNUSED(dialogName)
applySettingsToAllEditors();
if (QAction *action = actionCollection()->action(QStringLiteral("toggle_editor_dark_theme"))) {
action->setChecked(AppSettings::editorDarkTheme());
}
});
dialog->show();
@@ -248,12 +265,30 @@ void MainWindow::onCurrentEditorChanged(SciEdit *editor)
// 连接当前编辑器的光标位置变化信号
if (editor) {
applySettingsToEditor(editor);
connect(editor, &SciEdit::cursorPosChanged, this, [this](int line, int column) {
m_cursorPosStatusLabel->setText(QString("Ln: %1 Col: %2").arg(line).arg(column));
});
}
}
void MainWindow::applySettingsToEditor(SciEdit *editor)
{
if (!editor) {
return;
}
editor->setTabWidth(AppSettings::tabWidth());
editor->setEditorFont(AppSettings::editorFont());
editor->applyTheme(AppSettings::editorDarkTheme());
}
void MainWindow::applySettingsToAllEditors()
{
for (int i = 0; i < m_tabWidget->count(); ++i) {
applySettingsToEditor(m_tabWidget->editorAt(i));
}
}
void MainWindow::onDocumentModified(int docIndex, bool modified)
{
Q_UNUSED(docIndex)

View File

@@ -31,6 +31,8 @@ private:
void setupActions();
void showSettings();
void applyLexer(const QString & lexer);
void applySettingsToEditor(SciEdit *editor);
void applySettingsToAllEditors();
void updateWindowTitle();
void updateStatusBar();
void updateActions();

View File

@@ -26,6 +26,7 @@ Template: https://github.com/KDE/kxmlgui/blob/master/src/ui_standards.rc
<Action name="view_zoom_out"/>
</Menu>-->
<Action name="toggle_wrap_mode" />
<Action name="toggle_editor_dark_theme" />
</Menu>
<Menu name="encoding"><text>E&amp;ncoding</text>
</Menu>

View File

@@ -1,22 +1,34 @@
#include "sciedit.h"
#include <SciLexer.h>
#include <QColor>
#include <QDebug>
static sptr_t toBgr(const QColor &color) {
return (color.blue() << 16) | (color.green() << 8) | color.red();
}
SciEdit::SciEdit(QWidget * parent)
: ScintillaEdit(parent)
{
connect(this, &ScintillaEditBase::updateUi, this, [this](){
int curPos = currentPos();
emit cursorPosChanged(lineFromPosition(curPos) + 1, column(curPos));
updateLineNumberMarginWidth();
});
// 连接文本变化信号
connect(this, QOverload<Scintilla::ModificationFlags, Scintilla::Position, Scintilla::Position, Scintilla::Position, const QByteArray &, Scintilla::Position, Scintilla::FoldLevel, Scintilla::FoldLevel>::of(&ScintillaEditBase::modified),
this, &SciEdit::textChanged);
setLineNumbersEnabled(true);
}
void SciEdit::setStyleFont(const QFont &font, int style)
{
styleSetFont(style, font.family().toUtf8().constData());
styleSetSizeFractional(0, long(font.pointSizeF() * SC_FONT_SIZE_MULTIPLIER));
styleSetSizeFractional(style, long(font.pointSizeF() * SC_FONT_SIZE_MULTIPLIER));
}
void SciEdit::setFolding(FoldType foldType, int margin)
@@ -48,14 +60,64 @@ void SciEdit::setFolding(FoldType foldType, int margin)
void SciEdit::setLexer(Scintilla::ILexer5 *lexer)
{
setILexer((sptr_t)lexer);
m_lexerName = lexer ? QString::fromUtf8(lexer->GetName()) : QString();
if (lexer == nullptr) {
clearDocumentStyle();
}
applyLexerStyles();
colourise(0, -1);
}
qDebug() << lexer->GetName() << "lexer-------------";
void SciEdit::setLineNumbersEnabled(bool enabled)
{
m_lineNumbersEnabled = enabled;
setMarginTypeN(0, SC_MARGIN_NUMBER);
updateLineNumberMarginWidth();
}
void SciEdit::setEditorFont(const QFont &font)
{
m_editorFont = font;
applyTheme(m_isDarkTheme);
}
void SciEdit::applyTheme(bool dark)
{
m_isDarkTheme = dark;
const QColor bg = dark ? QColor(0x1e, 0x1e, 0x1e) : QColor(0xff, 0xff, 0xff);
const QColor fg = dark ? QColor(0xd4, 0xd4, 0xd4) : QColor(0x00, 0x00, 0x00);
const QColor caret = dark ? QColor(0xff, 0xff, 0xff) : QColor(0x00, 0x00, 0x00);
const QColor selectionBg = dark ? QColor(0x26, 0x4f, 0x78) : QColor(0xcc, 0xe8, 0xff);
const QColor caretLineBg = dark ? QColor(0x2a, 0x2a, 0x2a) : QColor(0xf5, 0xf5, 0xf5);
const QColor lineNumberFg = dark ? QColor(0x85, 0x85, 0x85) : QColor(0x80, 0x80, 0x80);
const QColor lineNumberBg = dark ? QColor(0x25, 0x25, 0x26) : QColor(0xf0, 0xf0, 0xf0);
styleResetDefault();
if (!m_editorFont.family().isEmpty()) {
setStyleFont(m_editorFont, STYLE_DEFAULT);
}
styleSetFore(STYLE_DEFAULT, toBgr(fg));
styleSetBack(STYLE_DEFAULT, toBgr(bg));
styleClearAll();
setCaretFore(toBgr(caret));
setSelFore(true, toBgr(fg));
setSelBack(true, toBgr(selectionBg));
setCaretLineVisible(true);
setCaretLineBack(toBgr(caretLineBg));
styleSetFore(STYLE_LINENUMBER, toBgr(lineNumberFg));
styleSetBack(STYLE_LINENUMBER, toBgr(lineNumberBg));
setMarginBackN(0, toBgr(lineNumberBg));
updateLineNumberMarginWidth();
applyLexerStyles();
}
void SciEdit::sendColor(unsigned int iMessage, uptr_t wParam, const QColor &color) const
{
sptr_t lParam = (color.blue() << 16) | (color.green() << 8) | color.red();
send(iMessage, wParam, lParam);
send(iMessage, wParam, toBgr(color));
}
void SciEdit::setMarkerDefine(int markerNumber, int markerSymbol)
@@ -67,3 +129,115 @@ void SciEdit::setMarkerDefine(int markerNumber, int markerSymbol)
sendColor(SCI_MARKERSETBACK, markerNumber, QColor(128, 128, 128));
}
}
void SciEdit::updateLineNumberMarginWidth()
{
if (!m_lineNumbersEnabled) {
setMarginWidthN(0, 0);
return;
}
const int lines = qMax<sptr_t>(1, lineCount());
const int digits = QString::number(lines).size();
if (digits == m_lastLineNumberDigits && marginWidthN(0) > 0) {
return;
}
m_lastLineNumberDigits = digits;
const QString sample = QString(digits, QLatin1Char('9'));
const QByteArray sampleBytes = sample.toUtf8();
const sptr_t width = textWidth(STYLE_LINENUMBER, sampleBytes.constData()) + 10;
setMarginWidthN(0, width);
}
void SciEdit::applyLexerStyles()
{
if (m_lexerName.isEmpty()) {
return;
}
const bool dark = m_isDarkTheme;
const QColor comment = dark ? QColor(0x6a, 0x99, 0x55) : QColor(0x00, 0x80, 0x00);
const QColor docComment = dark ? QColor(0x60, 0x80, 0x4f) : QColor(0x00, 0x80, 0x00);
const QColor keyword = dark ? QColor(0x56, 0x9c, 0xd6) : QColor(0x00, 0x00, 0xff);
const QColor type = dark ? QColor(0x4e, 0xc9, 0xb0) : QColor(0x2b, 0x91, 0xaf);
const QColor number = dark ? QColor(0xb5, 0xce, 0xa8) : QColor(0x09, 0x86, 0x58);
const QColor string = dark ? QColor(0xce, 0x91, 0x78) : QColor(0xa3, 0x15, 0x15);
const QColor preprocessor = dark ? QColor(0xc5, 0x86, 0xc0) : QColor(0xaf, 0x00, 0xdb);
const QColor tag = dark ? QColor(0x56, 0x9c, 0xd6) : QColor(0x80, 0x00, 0x00);
const QColor attr = dark ? QColor(0x9c, 0xdc, 0xfe) : QColor(0xff, 0x00, 0x00);
auto setFore = [&](int style, const QColor &c) {
styleSetFore(style, toBgr(c));
};
auto setBold = [&](int style, bool b) {
styleSetBold(style, b);
};
if (m_lexerName == QStringLiteral("cpp")) {
setFore(SCE_C_COMMENT, comment);
setFore(SCE_C_COMMENTLINE, comment);
setFore(SCE_C_COMMENTDOC, docComment);
setFore(SCE_C_COMMENTLINEDOC, docComment);
setFore(SCE_C_COMMENTDOCKEYWORD, docComment);
setFore(SCE_C_COMMENTDOCKEYWORDERROR, docComment);
setFore(SCE_C_NUMBER, number);
setFore(SCE_C_STRING, string);
setFore(SCE_C_CHARACTER, string);
setFore(SCE_C_PREPROCESSOR, preprocessor);
setFore(SCE_C_WORD, keyword);
setBold(SCE_C_WORD, true);
setFore(SCE_C_WORD2, type);
setBold(SCE_C_WORD2, true);
} else if (m_lexerName == QStringLiteral("python")) {
setFore(SCE_P_COMMENTLINE, comment);
setFore(SCE_P_NUMBER, number);
setFore(SCE_P_STRING, string);
setFore(SCE_P_CHARACTER, string);
setFore(SCE_P_WORD, keyword);
setBold(SCE_P_WORD, true);
setFore(SCE_P_WORD2, type);
setBold(SCE_P_WORD2, true);
setFore(SCE_P_DECORATOR, preprocessor);
setFore(SCE_P_CLASSNAME, type);
setFore(SCE_P_DEFNAME, type);
} else if (m_lexerName == QStringLiteral("hypertext")) {
setFore(SCE_H_COMMENT, comment);
setFore(SCE_H_TAG, tag);
setFore(SCE_H_TAGUNKNOWN, tag);
setFore(SCE_H_ATTRIBUTE, attr);
setFore(SCE_H_ATTRIBUTEUNKNOWN, attr);
setFore(SCE_H_NUMBER, number);
setFore(SCE_H_DOUBLESTRING, string);
setFore(SCE_H_SINGLESTRING, string);
setFore(SCE_H_ENTITY, preprocessor);
} else if (m_lexerName == QStringLiteral("xml")) {
setFore(SCE_H_COMMENT, comment);
setFore(SCE_H_TAG, tag);
setFore(SCE_H_TAGUNKNOWN, tag);
setFore(SCE_H_ATTRIBUTE, attr);
setFore(SCE_H_ATTRIBUTEUNKNOWN, attr);
setFore(SCE_H_NUMBER, number);
setFore(SCE_H_DOUBLESTRING, string);
setFore(SCE_H_SINGLESTRING, string);
setFore(SCE_H_ENTITY, preprocessor);
} else if (m_lexerName == QStringLiteral("json")) {
setFore(SCE_JSON_NUMBER, number);
setFore(SCE_JSON_STRING, string);
setFore(SCE_JSON_PROPERTYNAME, attr);
setFore(SCE_JSON_OPERATOR, preprocessor);
setFore(SCE_JSON_LINECOMMENT, comment);
setFore(SCE_JSON_BLOCKCOMMENT, comment);
} else if (m_lexerName == QStringLiteral("css")) {
setFore(SCE_CSS_COMMENT, comment);
setFore(SCE_CSS_CLASS, attr);
setFore(SCE_CSS_ID, attr);
setFore(SCE_CSS_TAG, tag);
setFore(SCE_CSS_ATTRIBUTE, attr);
setFore(SCE_CSS_DOUBLESTRING, string);
setFore(SCE_CSS_SINGLESTRING, string);
setFore(SCE_CSS_VALUE, string);
setFore(SCE_CSS_IDENTIFIER, keyword);
setBold(SCE_CSS_IDENTIFIER, true);
}
}

View File

@@ -3,6 +3,9 @@
#include <ScintillaEdit.h>
#include <ILexer.h>
#include <QFont>
#include <QString>
class SciEdit : public ScintillaEdit
{
Q_OBJECT
@@ -18,6 +21,9 @@ public:
void setStyleFont(const QFont & font, int style = STYLE_DEFAULT);
void setFolding(FoldType foldType, int margin = 2);
void setLexer(Scintilla::ILexer5 * lexer);
void setLineNumbersEnabled(bool enabled);
void setEditorFont(const QFont &font);
void applyTheme(bool dark);
void sendColor(unsigned int iMessage, uptr_t wParam, const QColor &col) const;
signals:
@@ -26,4 +32,12 @@ signals:
private:
void setMarkerDefine(int markerNumber, int markerSymbol);
void updateLineNumberMarginWidth();
void applyLexerStyles();
bool m_lineNumbersEnabled = false;
int m_lastLineNumberDigits = 0;
bool m_isDarkTheme = false;
QFont m_editorFont;
QString m_lexerName;
};

View File

@@ -1,11 +1,41 @@
#include "tabwidget.h"
#include "sciedit.h"
#include "documentmanager.h"
#include "appsettings.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <ILexer.h>
#include <Lexilla.h>
static QString lexerNameForDocumentLanguage(const QString &language)
{
if (language == QStringLiteral("C++") ||
language == QStringLiteral("C") ||
language == QStringLiteral("C/C++ Header")) {
return QStringLiteral("cpp");
}
if (language == QStringLiteral("Python")) {
return QStringLiteral("python");
}
if (language == QStringLiteral("HTML")) {
return QStringLiteral("hypertext");
}
if (language == QStringLiteral("XML")) {
return QStringLiteral("xml");
}
if (language == QStringLiteral("JSON")) {
return QStringLiteral("json");
}
if (language == QStringLiteral("CSS")) {
return QStringLiteral("css");
}
return QString();
}
TabWidget::TabWidget(DocumentManager *documentManager, QWidget *parent)
: QTabWidget(parent)
, m_documentManager(documentManager)
@@ -73,6 +103,17 @@ int TabWidget::openDocument(const QString &filePath)
// 设置编辑器内容
QString content = m_documentManager->getDocumentContent(docId);
editor->setText(content.toUtf8().constData());
editor->setTabWidth(AppSettings::tabWidth());
editor->setEditorFont(AppSettings::editorFont());
editor->applyTheme(AppSettings::editorDarkTheme());
const QString lexerName = lexerNameForDocumentLanguage(m_documentManager->getDocumentLanguage(docId));
if (lexerName.isEmpty()) {
editor->setLexer(nullptr);
} else {
Scintilla::ILexer5 *lexer = CreateLexer(lexerName.toStdString().c_str());
editor->setLexer(lexer);
}
// 重新连接信号
connectEditorSignals(editor);
@@ -136,7 +177,17 @@ bool TabWidget::saveCurrentDocumentAs()
QByteArray content = editor->getText(editor->textLength());
m_documentManager->setDocumentContent(docId, QString::fromUtf8(content));
return m_documentManager->saveDocumentAs(docId, fileName);
const bool saved = m_documentManager->saveDocumentAs(docId, fileName);
if (saved) {
const QString lexerName = lexerNameForDocumentLanguage(m_documentManager->getDocumentLanguage(docId));
if (lexerName.isEmpty()) {
editor->setLexer(nullptr);
} else {
Scintilla::ILexer5 *lexer = CreateLexer(lexerName.toStdString().c_str());
editor->setLexer(lexer);
}
}
return saved;
}
void TabWidget::closeCurrentTab()
@@ -349,6 +400,9 @@ void TabWidget::onEditorTextChanged()
SciEdit *TabWidget::createEditor()
{
SciEdit *editor = new SciEdit(this);
editor->setTabWidth(AppSettings::tabWidth());
editor->setEditorFont(AppSettings::editorFont());
editor->applyTheme(AppSettings::editorDarkTheme());
return editor;
}
@@ -379,4 +433,4 @@ void TabWidget::disconnectEditorSignals(SciEdit *editor)
if (editor) {
disconnect(editor, &SciEdit::textChanged, this, &TabWidget::onEditorTextChanged);
}
}
}