Skip to content

Commit 01e1e63

Browse files
committed
Symbol picker
1 parent 2d42178 commit 01e1e63

24 files changed

+858
-62
lines changed

core/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ set(SOURCES
3333
katvan_parsing.cpp
3434
katvan_previewerview.cpp
3535
katvan_spellchecker.cpp
36-
katvan_typstdriverwrapper.cpp
36+
katvan_symbolpicker.cpp
3737
katvan_text_utils.cpp
38+
katvan_typstdriverwrapper.cpp
3839
)
3940

4041
qt_add_resources(SOURCES themes/themes.qrc)

core/katvan_codemodel.cpp

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,30 @@ static bool isLeftLeaningState(State state)
220220
|| state == State::CONTENT_RAW_BLOCK;
221221
}
222222

223+
CodeModel::EnvironmentType CodeModel::classifyEnvironment(int pos) const
224+
{
225+
QTextBlock block = d_document->findBlock(pos);
226+
if (!block.isValid()) {
227+
return EnvironmentType::UNKNOWN;
228+
}
229+
230+
auto span = spanAtPosition(block, pos);
231+
if (!span) {
232+
return EnvironmentType::CONTENT;
233+
}
234+
235+
if (parsing::isContentHolderStateKind(span->state)) {
236+
return EnvironmentType::CONTENT;
237+
}
238+
else if (parsing::isCodeHolderStateKind(span->state)) {
239+
return EnvironmentType::CODE;
240+
}
241+
else if (parsing::isMathHolderStateKind(span->state)) {
242+
return EnvironmentType::MATH;
243+
}
244+
return EnvironmentType::OTHER;
245+
}
246+
223247
std::optional<int> CodeModel::findMatchingBracket(int pos) const
224248
{
225249
QTextBlock block = d_document->findBlock(pos);
@@ -415,22 +439,13 @@ std::optional<QChar> CodeModel::getMatchingCloseBracket(QTextCursor cursor, QCha
415439
prevChar = cursor.block().text().at(cursor.positionInBlock() - 1);
416440
}
417441

418-
bool isInCode = state == State::CODE_BLOCK
419-
|| state == State::CODE_ARGUMENTS
420-
|| state == State::CODE_LINE;
442+
bool isInCode = parsing::isCodeHolderStateKind(state);
443+
bool isInMath = parsing::isMathHolderStateKind(state);
444+
bool isInContent = parsing::isContentHolderStateKind(state);
421445

422446
bool isCodeFunctionCall = state == State::CODE_VARIABLE_NAME // Possibly part of a function call
423447
|| state == State::CODE_FUNCTION_NAME; // Definitely part of a function call
424448

425-
bool isInMath = state == State::MATH
426-
|| state == State::MATH_ARGUMENTS;
427-
428-
bool isInContent = state == State::CONTENT
429-
|| state == State::CONTENT_BLOCK
430-
|| state == State::CONTENT_HEADING
431-
|| state == State::CONTENT_EMPHASIS
432-
|| state == State::CONTENT_STRONG_EMPHASIS;
433-
434449
bool isInRaw = state == State::CONTENT_RAW
435450
|| state == State::CONTENT_RAW_BLOCK;
436451

@@ -481,4 +496,22 @@ std::optional<QChar> CodeModel::getMatchingCloseBracket(QTextCursor cursor, QCha
481496
return std::nullopt;
482497
}
483498

499+
QString CodeModel::getSymbolExpression(const QString& symbolName, int pos) const
500+
{
501+
EnvironmentType env = classifyEnvironment(pos);
502+
if (env == EnvironmentType::UNKNOWN || symbolName.isEmpty()) {
503+
return QString();
504+
}
505+
506+
if (env == EnvironmentType::OTHER || env == EnvironmentType::CODE) {
507+
return symbolName;
508+
}
509+
if (env == EnvironmentType::MATH && symbolName.startsWith("sym.")) {
510+
return symbolName.sliced(4);
511+
}
512+
return QLatin1Char('#') + symbolName;
513+
}
514+
484515
}
516+
517+
#include "moc_katvan_codemodel.cpp"

core/katvan_codemodel.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,24 @@ class CodeModel : public QObject
8181
Q_OBJECT
8282

8383
public:
84+
enum class EnvironmentType
85+
{
86+
UNKNOWN = 0,
87+
CONTENT,
88+
CODE,
89+
MATH,
90+
OTHER,
91+
};
92+
Q_ENUM(EnvironmentType);
93+
8494
CodeModel(QTextDocument* document)
8595
: QObject(document)
8696
, d_document(document) {}
8797

98+
// Perform rough classification on the type of environment (content, math,
99+
// etc) that the given global position is in.
100+
EnvironmentType classifyEnvironment(int pos) const;
101+
88102
// If there is a delimiting bracket at the given global position,
89103
// find the position of the matching (opening/closing) bracket.
90104
std::optional<int> findMatchingBracket(int pos) const;
@@ -116,6 +130,10 @@ class CodeModel : public QObject
116130
// if _openBracket_ is inserted at the given cursor's position.
117131
std::optional<QChar> getMatchingCloseBracket(QTextCursor cursor, QChar openBracket) const;
118132

133+
// Get the correct Typst expression to insert the given symbol at the given
134+
// global position
135+
QString getSymbolExpression(const QString& symbolName, int pos) const;
136+
119137
private:
120138
// Find the inner most state span still in effect at the given global position
121139
std::optional<StateSpan> spanAtPosition(QTextBlock block, int globalPos) const;

core/katvan_completionmanager.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ constexpr int ICON_MARGIN_PX = 3;
5454

5555
CompletionListModel::CompletionListModel(QObject* parent)
5656
: QAbstractListModel(parent)
57-
, d_symbolFont("Noto Sans Math")
57+
, d_symbolFont(utils::SYMBOL_FONT_FAMILY)
5858
{
5959
}
6060

@@ -107,7 +107,7 @@ QVariant CompletionListModel::data(const QModelIndex& index, int role) const
107107
QJsonObject kind = obj["kind"].toObject();
108108
QString symbol = kind["symbol"].toString();
109109
if (!symbol.isEmpty()) {
110-
return utils::fontIcon(symbol[0], d_symbolFont);
110+
return utils::fontIcon(utils::firstCodepointOf(symbol), d_symbolFont);
111111
}
112112
}
113113
}

core/katvan_editor.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ QMenu* Editor::createInsertMenu()
252252
insertInlineMathAction->setIcon(utils::fontIcon(QLatin1Char('$')));
253253
insertInlineMathAction->setShortcut(Qt::CTRL | Qt::Key_M);
254254

255+
menu->addAction("&Symbol...", this, &Editor::showSymbolPicker);
256+
255257
return menu;
256258
}
257259

@@ -1154,6 +1156,15 @@ void Editor::insertSurroundingMarks(QString before, QString after)
11541156
setTextCursor(cursor);
11551157
}
11561158

1159+
void Editor::insertSymbol(const QString& symbolName)
1160+
{
1161+
QTextCursor cursor = textCursor();
1162+
QString expression = d_codeModel->getSymbolExpression(symbolName, cursor.position());
1163+
1164+
cursor.insertText(expression);
1165+
setTextCursor(cursor);
1166+
}
1167+
11571168
int Editor::lineNumberGutterWidth()
11581169
{
11591170
int digits = 1;

core/katvan_editor.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public slots:
7373
void showToolTip(QPoint windowPos, const QString& text, const QUrl& detailsUrl);
7474
void showToolTipAtLocation(int line, int column, const QString& text, const QUrl& detailsUrl);
7575

76+
void insertSymbol(const QString& symbolName);
77+
7678
protected:
7779
bool event(QEvent* event) override;
7880
void contextMenuEvent(QContextMenuEvent* event) override;
@@ -129,6 +131,7 @@ private slots:
129131
void fontZoomFactorChanged(qreal factor);
130132
void toolTipRequested(int blockNumber, int charOffset, QPoint widgetPos);
131133
void goToDefinitionRequested(int blockNumber, int charOffset);
134+
void showSymbolPicker();
132135

133136
private:
134137
QWidget* d_leftLineNumberGutter;

core/katvan_parsing.cpp

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -370,25 +370,38 @@ void TokenStream::releaseConsumedTokens()
370370
d_pos = 0;
371371
}
372372

373-
Parser::Parser(QStringView text, const QList<ParserState::Kind>& initialStates)
374-
: d_text(text)
375-
, d_tokenStream(text)
376-
, d_atContentStart(false)
377-
, d_startMarker(0)
378-
, d_endMarker(0)
373+
bool isContentHolderStateKind(ParserState::Kind state)
379374
{
380-
d_stateStack.append(ParserState{ ParserState::Kind::CONTENT, 0, true });
381-
for (ParserState::Kind state : initialStates) {
382-
d_stateStack.append(ParserState{ state, 0, true });
383-
}
375+
// States that can have nested content states in them
376+
return state == ParserState::Kind::CONTENT
377+
|| state == ParserState::Kind::CONTENT_BLOCK
378+
|| state == ParserState::Kind::CONTENT_HEADING
379+
|| state == ParserState::Kind::CONTENT_EMPHASIS
380+
|| state == ParserState::Kind::CONTENT_STRONG_EMPHASIS;
384381
}
385382

386-
void Parser::addListener(ParsingListener& listener, bool finalizeOnEnd)
383+
bool isMathHolderStateKind(ParserState::Kind state)
387384
{
388-
d_listeners.append(std::ref(listener));
389-
if (finalizeOnEnd) {
390-
d_finalizingListeners.append(std::ref(listener));
391-
}
385+
return state == ParserState::Kind::MATH
386+
|| state == ParserState::Kind::MATH_ARGUMENTS;
387+
}
388+
389+
bool isCodeHolderStateKind(ParserState::Kind state)
390+
{
391+
return state == ParserState::Kind::CODE_BLOCK
392+
|| state == ParserState::Kind::CODE_LINE
393+
|| state == ParserState::Kind::CODE_ARGUMENTS;
394+
}
395+
396+
bool isCodeStateKind(ParserState::Kind state)
397+
{
398+
return isCodeHolderStateKind(state)
399+
|| state == ParserState::Kind::CODE_VARIABLE_NAME
400+
|| state == ParserState::Kind::CODE_FUNCTION_NAME
401+
|| state == ParserState::Kind::CODE_NUMERIC_LITERAL
402+
|| state == ParserState::Kind::CODE_KEYWORD
403+
|| state == ParserState::Kind::CODE_EXPRESSION_CHAIN
404+
|| state == ParserState::Kind::CODE_STRING_EXPRESSION;
392405
}
393406

394407
static bool isBlockScopedState(const ParserState& state)
@@ -401,36 +414,43 @@ static bool isBlockScopedState(const ParserState& state)
401414

402415
static bool isContentHolderState(const ParserState& state)
403416
{
404-
// States that can have nested content states in them
405-
return state.kind == ParserState::Kind::CONTENT
406-
|| state.kind == ParserState::Kind::CONTENT_BLOCK
407-
|| state.kind == ParserState::Kind::CONTENT_HEADING
408-
|| state.kind == ParserState::Kind::CONTENT_EMPHASIS
409-
|| state.kind == ParserState::Kind::CONTENT_STRONG_EMPHASIS;
417+
return isContentHolderStateKind(state.kind);
410418
}
411419

412420
static bool isMathHolderState(const ParserState& state)
413421
{
414-
return state.kind == ParserState::Kind::MATH
415-
|| state.kind == ParserState::Kind::MATH_ARGUMENTS;
422+
return isMathHolderStateKind(state.kind);
416423
}
417424

418425
static bool isCodeHolderState(const ParserState& state)
419426
{
420-
return state.kind == ParserState::Kind::CODE_BLOCK
421-
|| state.kind == ParserState::Kind::CODE_LINE
422-
|| state.kind == ParserState::Kind::CODE_ARGUMENTS;
427+
return isCodeHolderStateKind(state.kind);
423428
}
424429

425430
static bool isCodeState(const ParserState& state)
426431
{
427-
return isCodeHolderState(state)
428-
|| state.kind == ParserState::Kind::CODE_VARIABLE_NAME
429-
|| state.kind == ParserState::Kind::CODE_FUNCTION_NAME
430-
|| state.kind == ParserState::Kind::CODE_NUMERIC_LITERAL
431-
|| state.kind == ParserState::Kind::CODE_KEYWORD
432-
|| state.kind == ParserState::Kind::CODE_EXPRESSION_CHAIN
433-
|| state.kind == ParserState::Kind::CODE_STRING_EXPRESSION;
432+
return isCodeStateKind(state.kind);
433+
}
434+
435+
Parser::Parser(QStringView text, const QList<ParserState::Kind>& initialStates)
436+
: d_text(text)
437+
, d_tokenStream(text)
438+
, d_atContentStart(false)
439+
, d_startMarker(0)
440+
, d_endMarker(0)
441+
{
442+
d_stateStack.append(ParserState{ ParserState::Kind::CONTENT, 0, true });
443+
for (ParserState::Kind state : initialStates) {
444+
d_stateStack.append(ParserState{ state, 0, true });
445+
}
446+
}
447+
448+
void Parser::addListener(ParsingListener& listener, bool finalizeOnEnd)
449+
{
450+
d_listeners.append(std::ref(listener));
451+
if (finalizeOnEnd) {
452+
d_finalizingListeners.append(std::ref(listener));
453+
}
434454
}
435455

436456
void Parser::parse()

core/katvan_parsing.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
#include <concepts>
2424
#include <functional>
25-
#include <optional>
2625
#include <span>
2726
#include <vector>
2827

@@ -143,6 +142,11 @@ struct ParserState
143142

144143
using ParserStateStack = QList<ParserState>;
145144

145+
bool isContentHolderStateKind(ParserState::Kind state);
146+
bool isMathHolderStateKind(ParserState::Kind state);
147+
bool isCodeHolderStateKind(ParserState::Kind state);
148+
bool isCodeStateKind(ParserState::Kind state);
149+
146150
class ParsingListener
147151
{
148152
public:

0 commit comments

Comments
 (0)