-
Notifications
You must be signed in to change notification settings - Fork 171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change printing to add ligature support #470
Conversation
src/gui/shellwidget/shellwidget.cpp
Outdated
@@ -294,7 +285,7 @@ int ShellWidget::put(const QString& text, int row, int column, | |||
int cols_changed = m_contents.put(text, row, column, fg, bg, sp, | |||
bold, italic, underline, undercurl); | |||
if (cols_changed > 0) { | |||
QRect rect = absoluteShellRect(row, column, 1, cols_changed); | |||
QRect rect = absoluteShellRect(row, 0, 1, m_contents.columns()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So every update turns into a full line update. Any idea on much slower this will make things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure. On the one hand it will make single character changes significantly longer but on the other hand, we're now calling one fillRect and one drawText for what could be entire lines, so in some cases it could make performance faster. I'm not sure how to reliably test it, but am willing to if you have a suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of tests in shellwidgets/test. The ones starting with bench_ should run repeated benchmarks.
For example to run the bench_scroll test you can call
$ ctest -R bench_scroll -VV
UpdateCTestConfiguration from :/home/raf/Code/neovim-qt/build/DartConfiguration.tcl
UpdateCTestConfiguration from :/home/raf/Code/neovim-qt/build/DartConfiguration.tcl
Test project /home/raf/Code/neovim-qt/build
Constructing a list of tests
Done constructing a list of tests
Updating test list for fixtures
Added 0 tests to meet fixture requirements
Checking test dependency graph...
Checking test dependency graph end
test 11
Start 11: bench_scroll
11: Test command: /home/raf/Code/neovim-qt/build/bin/bench_scroll
11: Test timeout computed to be: 10000000
11: ********* Start testing of Test *********
11: Config: Using QtTest library 5.10.1, Qt 5.10.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 8.2.0)
11: PASS : Test::initTestCase()
11: PASS : Test::benchScrollRegion()
11: RESULT : Test::benchScrollRegion():
11: 0.42 msecs per iteration (total: 54, iterations: 128)
11: PASS : Test::cleanupTestCase()
11: Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 219ms
11: ********* Finished testing of Test *********
1/1 Test #11: bench_scroll ..................... Passed 0.24 sec
The following tests passed:
bench_scroll
100% tests passed, 0 tests failed out of 1
Which gives the average time for one test execution.
We could try something similar using the ShellWidget class directly. For comparison here what we would want is two tests, one with using ligatures and one without.
If you check test_shellcontents.cpp you'll see that you can load data from a file instead of doing it programaticaly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this
diff --git a/src/gui/shellwidget/test/CMakeLists.txt b/src/gui/shellwidget/test/CMakeLists.txt
index 92ff991..7fe254b 100644
--- a/src/gui/shellwidget/test/CMakeLists.txt
+++ b/src/gui/shellwidget/test/CMakeLists.txt
@@ -15,3 +15,4 @@ add_xtest(test_shellcontents)
add_xtest(test_shellwidget)
add_xtest(bench_scroll)
add_xtest(bench_cell)
+add_xtest(bench_widgetpaint)
diff --git a/src/gui/shellwidget/test/bench_widgetpaint.cpp b/src/gui/shellwidget/test/bench_widgetpaint.cpp
new file mode 100644
index 0000000..f220450
--- /dev/null
+++ b/src/gui/shellwidget/test/bench_widgetpaint.cpp
@@ -0,0 +1,23 @@
+#include <QtTest/QtTest>
+#include "shellwidget.h"
+
+#if defined(Q_OS_WIN) && defined(USE_STATIC_QT)
+#include <QtPlugin>
+Q_IMPORT_PLUGIN (QWindowsIntegrationPlugin);
+#endif
+
+class Test: public QObject
+{
+ Q_OBJECT
+private slots:
+ void benchPaint() {
+ QBENCHMARK {
+ auto s = ShellWidget::fromFile("../test/buffer.c");
+ s->show();
+ s->repaint();
+ }
+ }
+};
+
+QTEST_MAIN(Test)
+#include "bench_widgetpaint.moc"
In this example I'm using one of the files in the repo which might not have many ligatures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heads up on that example
- that path is probably wrong
- before forcing a repaint you will likely want to resize
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It actually has a decent amount of them, but I think this is more about rendering regular code anyway.
Time on master:
13: PASS : Test::initTestCase()
13: PASS : Test::benchPaint()
13: RESULT : Test::benchPaint():
13: 3.5 msecs per iteration (total: 56, iterations: 16)
13: PASS : Test::cleanupTestCase()
13: Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 256ms
Time on ligature branch:
13: PASS : Test::initTestCase()
13: PASS : Test::benchPaint()
13: RESULT : Test::benchPaint():
13: 4.6 msecs per iteration (total: 74, iterations: 16)
13: PASS : Test::cleanupTestCase()
13: Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 230ms
This isn't necessarily representative. I've run both a bunch of times and got higher and lower results with both. In general it would seem the new code has less consistent draw time. I'll add a few more lines to the test and try some more testing after adding the property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A slightly better test case. Sorry about the formatting mess. The jpg file is just so I can examine what the test ended up doing.
#include <QtTest/QtTest>
#include <QScreen>
#include "shellwidget.h"
#include "helpers.h"
#if defined(Q_OS_WIN) && defined(USE_STATIC_QT)
#include <QtPlugin>
Q_IMPORT_PLUGIN (QWindowsIntegrationPlugin);
#endif
class Test: public QObject
{
Q_OBJECT
ShellWidget *s;
private slots:
void init() {
// FIXME: path
QFileInfo fi("../../../../../src/gui/shellwidget/test/shellcontents/shell2.txt");
s = ShellWidget::fromFile(fi.absoluteFilePath());
s->resizeShell(80, 120);
s->show();
}
void cleanupTestCase() {
// save a image of the widget after the last test
s->grab().save("widgetpaint.png");
}
void benchPaint() {
QBENCHMARK {
s->repaint();
}
}
};
QTEST_MAIN(Test)
#include "bench_widgetpaint.moc"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I've included a working benchmark with fixes for the path here 736f57a
We should also check what happens when fonts are not monospace. I am very tempted to put this behind some kind of feature flag/option, I suspect the performance impact will be significant. |
If the font is not monospace this will most likely be problematic. I assumed those weren't supported. A configurable flag sounds reasonable. If it's off we'll only update the changed sections as in the past and the loop will always stop after one step, otherwise we keep the new flow? |
We kind of do, via |
I could make the property be
Where would this flag go? |
Lets start with off by default. The actual setting can be stored in the ShellWidget class. We would need a way for users change this at run time. Similar to what is done with setGuiFont. |
Fira Mono doesn't have ligatures afaik. They're a font feature added by Fira Code. |
Ok at least its consistent. If you get a feature flag going I'm ok with merging this. |
@@ -6,6 +6,8 @@ endif () | |||
|
|||
function(add_xtest SOURCE_NAME) | |||
add_executable(${SOURCE_NAME} ${SOURCE_NAME}.cpp) | |||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR} TEST_SOURCE_DIR) | |||
add_compile_definitions(${SOURCE_NAME} TEST_SOURCE_DIR="${TEST_SOURCE_DIR}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems add_compile_definitions
does not work with older versions of cmake. This is why the tests are failing.
I think (untested) the alternative is to use add_definitions(${SOURCE_NAME} -DTEST_SOURCE_DIR="${TEST_SOURCE_DIR}")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's that, but also bench_widgetpaint segfaults on win64.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That add_definitions
line failed on linux. The following works fine, outside of the function:
add_definitions(-DTEST_SOURCE_DIR="${TEST_SOURCE_DIR}")
Since add_definitions
only affects the directory and its subdirs I think that's fine.
@@ -6,12 +6,16 @@ endif () | |||
|
|||
function(add_xtest SOURCE_NAME) | |||
add_executable(${SOURCE_NAME} ${SOURCE_NAME}.cpp) | |||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR} TEST_SOURCE_DIR) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this line out of this function, before the add_definition.
The segfault happens because the test fails to open the file. Since TEST_SOURCE_DIR is no defined.
TO_NATIVE_PATH results in malformed path strings in VS (the backslashes aren't escaped). Changing it to TO_CMAKE_PATH did the trick. |
@equalsraf, the last commit I pushed added an RPC option to enable ligature rendering via |
src/gui/shellwidget/shellwidget.cpp
Outdated
} else { | ||
pen.setColor(m_fgColor); | ||
// end_row is inclusive | ||
QChar *rowStr = new QChar[m_contents.columns()]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have two nitpicks with how this is done here
- I would really like to avoid the rowStr copy when ligaturemode is off
- Part of this loop is used to fill rowStr from cell contents. Maybe we can just add a method ShellContents::getRow(n) that allocates the array(s) and fills it up return all the pieces that need to be draw.
Sorry @Nuclearo I dropped out the face of the earth. The option looks fine. I've left a comment about the painting. |
No problem, I'm looking into what can be done about the copying. |
That's exactly the patch I've been looking for. Now I use nvim-qt version with ligature support locally. Hope it gets merged soon enough! |
any update? |
I think this is done, circleci failure seems to be at the server... |
I'd love to see this feature! Any updates on this? |
and there we go, squashed because it was getting too full of merge commits and passing tests nicely. |
I noticed that undercurl (like the one in spell checking) seems to be broken with this PR 😔 |
@hellbuster Can you write a test or give me a config that causes this issue? |
@Nuclearo I could reproduce with |
I tried to get something useful by running through gdb. When I do |
Yeah, I moved the background painting outside of the loop as a bit of an optimization and it backfired, silly me... This new patch should fix it. Thanks again for testing, @hellbuster! |
What's the status of this PR right now? It's been a while since I did C++ but the merge conflicts seem non-trivial. |
Any particular reason this hasn't been merged yet? I seem to remember @equalsraf saying he was ok with merging. |
BTW @dutchgecko, FVim is a frontend for neovim written in F# + Avalonia, and it supports ligatures. The latest three versions are broken on windows though, so you're gonna want v0.2-192-gb6a3318 if you're on windows. See yatli/fvim#132 and yatli/fvim#132 (comment). |
I've been trying to keep the PR mergeable for a while, but refactoring in master makes these merges progressively more difficult. After all, this changes the entire flow of paintEvent, including indentations. At this point I might even prefer to just rewrite it from scratch, implementing a couple of improvements that I left for after it was merged. |
I'd like this to be merged soon, only thing stopping me from using neovim-qt is lack of ligature support. |
This feature is on my radar... We should support ligatures. Clearly there is enough user demand to justify the development work! 😁 Sorry for all of the merge conflicts, I am (mostly) the one responsible. My plan is to modularize various rendering tasks into functions. This should at the least make re-writing the I would also like to do some research on the QT Font Engine to see if ligatures can be pre-detected. The current rendering scheme would break certain non-monospace fonts and features (guifontwide). Not a big problem for now, since the change is gated in ginit.vim... This is your feature, let me know how you would like to handle the work. I am happy to do any of the following:
Thanks! |
Ligatures |
@RealXIHNIK
|
Adds support for a :GuiRenderLigatures command, which will change the underlying ShellWidget render logic to paint segments of text with the same styling. Previously text was rendered character-by-character on the grid, preventing font ligatures from rendering. Code re-based from: equalsraf#470
This work would not have been done without @Nucearo 's efforts: equalsraf#470 Uses QTextLayout to render segments of similarly styled cells. QTextLayout provides greater control over Qt's font-rendering engine via QGlyphRun. With QGlyphRun, we are able to identify individual characters and their positions. QGlyphRun should provide a means of correcting the spacing for non-perfect monospace fonts. It also allows for identification of when a character sequence is converted to a glyph.
Hi everyone, sorry for being so late, lost access to my account for a while. |
This work would not have been done without @Nucearo 's efforts: equalsraf#470 Uses QTextLayout to render segments of similarly styled cells. QTextLayout provides greater control over Qt's font-rendering engine via QGlyphRun. With QGlyphRun, we are able to identify individual characters and their positions. QGlyphRun should provide a means of correcting the spacing for non-perfect monospace fonts. It also allows for identification of when a character sequence is converted to a glyph.
Adds support for a :GuiRenderLigatures command, which will change the underlying ShellWidget render logic to paint segments of text with the same styling. Previously text was rendered character-by-character on the grid, preventing font ligatures from rendering. Code re-based from: equalsraf#470
This work would not have been done without @Nucearo 's efforts: equalsraf#470 Uses QTextLayout to render segments of similarly styled cells. QTextLayout provides greater control over Qt's font-rendering engine via QGlyphRun. With QGlyphRun, we are able to identify individual characters and their positions. QGlyphRun should provide a means of correcting the spacing for non-perfect monospace fonts. It also allows for identification of when a character sequence is converted to a glyph.
Adds support for a :GuiRenderLigatures command, which will change the underlying ShellWidget render logic to paint segments of text with the same styling. Previously text was rendered character-by-character on the grid, preventing font ligatures from rendering. Code re-based from: equalsraf#470
This work would not have been done without @Nucearo 's efforts: equalsraf#470 Uses QTextLayout to render segments of similarly styled cells. QTextLayout provides greater control over Qt's font-rendering engine via QGlyphRun. With QGlyphRun, we are able to identify individual characters and their positions. QGlyphRun should provide a means of correcting the spacing for non-perfect monospace fonts. It also allows for identification of when a character sequence is converted to a glyph.
This work would not have been done without @Nucearo 's efforts: equalsraf#470 Uses QTextLayout to render segments of similarly styled cells. QTextLayout provides greater control over Qt's font-rendering engine via QGlyphRun. With QGlyphRun, we are able to identify individual characters and their positions. QGlyphRun should provide a means of correcting the spacing for non-perfect monospace fonts. It also allows for identification of when a character sequence is converted to a glyph.
This work would not have been done without @Nucearo 's efforts: equalsraf#470 Uses QTextLayout to render segments of similarly styled cells. QTextLayout provides greater control over Qt's font-rendering engine via QGlyphRun. With QGlyphRun, we are able to identify individual characters and their positions. QGlyphRun should provide a means of correcting the spacing for non-perfect monospace fonts. It also allows for identification of when a character sequence is converted to a glyph.
Adds support for a :GuiRenderLigatures command, which will change the underlying ShellWidget render logic to paint segments of text with the same styling. Previously text was rendered character-by-character on the grid, preventing font ligatures from rendering. Code re-based from: equalsraf#470
This work would not have been done without @Nucearo 's efforts: equalsraf#470 Uses QTextLayout to render segments of similarly styled cells. QTextLayout provides greater control over Qt's font-rendering engine via QGlyphRun. With QGlyphRun, we are able to identify individual characters and their positions. QGlyphRun should provide a means of correcting the spacing for non-perfect monospace fonts. It also allows for identification of when a character sequence is converted to a glyph.
Adds support for a :GuiRenderLigatures command, which will change the underlying ShellWidget render logic to paint segments of text with the same styling. Previously text was rendered character-by-character on the grid, preventing font ligatures from rendering. Code re-based from: #470
This work would not have been done without @Nucearo 's efforts: #470 Uses QTextLayout to render segments of similarly styled cells. QTextLayout provides greater control over Qt's font-rendering engine via QGlyphRun. With QGlyphRun, we are able to identify individual characters and their positions. QGlyphRun should provide a means of correcting the spacing for non-perfect monospace fonts. It also allows for identification of when a character sequence is converted to a glyph.
Thanks @Nuclearo! Ligatures are now available in The feature should be available for release |
Adds support for a :GuiRenderLigatures command, which will change the underlying ShellWidget render logic to paint segments of text with the same styling. Previously text was rendered character-by-character on the grid, preventing font ligatures from rendering. Code re-based from: equalsraf/neovim-qt#470
Adds support for a :GuiRenderLigatures command, which will change the underlying ShellWidget render logic to paint segments of text with the same styling. Previously text was rendered character-by-character on the grid, preventing font ligatures from rendering. Code re-based from: equalsraf/neovim-qt#470
Solves #166
I've changed the text drawing function to
The new printing flow is similar to Konsole's. It collects new characters with the same font and colors, then draws that chunk all at once.
Testing:
I tested with code using the Fira Code font:, and some sample Chinese and Korean texts to test double width characters and all seemed to render fine.