I finally found some time to revisit this.
Closed this issue · 18 comments
(I could find how to reopen the issue 83#)
I finally found some time to revisit this.
I was able to add the following functionallities:
- Syntaxhighlighting
- Linenumbers && highlighted current linenumber
- Highlight current Line
- Selection works as expected
all handled by config options for textArea.
Two things though:
- It could not fix the jumping of the linenumbers - I tried to remove the padding as you suggested, but it still jumps back and forth
- My syntaxhighlighting method looks okay, but if you switch it on an off ... there is movement in the text, so I make mistakes in my alignment of the text
Maybe you have a tip on how to deal with these two things
This is the code for the renderer in TextAreaWidget (I'm using a open repo for this with only the nesseary code - whole monomer took quite long to build everytime):
render wenv node renderer = do
let font = fromMaybe def (style ^? L.text . _Just . L.font . _Just)
let fontSize = fromMaybe def (style ^? L.text . _Just . L.fontSize . _Just)
-- Highlight current line
let textLinesRect = wenv ^. L.viewport & L.x +~ lineNumberWidth
drawInScissor renderer True textLinesRect $
drawInTranslation renderer offset $ do
case _tacCurrentLineColor config of
Nothing -> return ()
color -> do
let indexCurrentLine = snd $ _tasCursorPos state
let currentLine = Seq.index textLines indexCurrentLine
let (Rect tx ty tw th) = _tlRect currentLine
let maxLineLength = Seq.foldlWithIndex (\startValue _ textLine -> do
let tmpLength = _rW (_tlRect textLine)
if tmpLength >= startValue
then tmpLength
else startValue) 0 textLines
drawRect renderer (Rect (-10) (ty-5) (10+maxLineLength) (th+10)) color Nothing
when selRequired $
forM_ selRects $ \rect ->
when (rect ^. L.w > 0) $
drawRect renderer rect (Just selColor) Nothing
-- Syntax Highlighting
case _tacSyntax config of
-- Apply syntax highlighting
Just (Just syntaxTree,syntaxMap) -> do
let textLines' = Seq.mapWithIndex (\index value -> value & L.fontSize .~ fontSize) textLines
MyDrawing.drawTextSyntaxed renderer style syntaxTree syntaxMap textLines'
-- No syntax highlighting
_ -> do
let textLines' = Seq.mapWithIndex (\index value -> value & L.fontSize .~ fontSize) textLines
forM_ textLines' (drawTextLine renderer style)
when caretRequired $
drawRect renderer caretRect (Just caretColor) Nothing
-- draw LineNumbers
let lineNumbersRect = wenv ^. L.viewport & L.w .~ lineNumberWidth
let otherStyle color = style & L.text . non def . L.fontColor ?~ color
let rH' = if textLines == Seq.empty
then 5
else do
let line = Seq.index textLines 0
let space = unFontSpace $ _tlFontSpaceV line
let height = _rH $ _tlRect line
height + space
let textLine t y = do
let wvp = fromMaybe (wenv ^. L.viewport) (removeOuterBounds style (wenv ^. L.viewport))
let rect' = wvp & L.x +~ 5 & L.y .~ y & L.w .~ 20 & L.h .~ 20
--let rect' = Rect (wenv ^. L.viewport ^. L.x + 5) y 20 20
def
& L.text .~ t
& L.rect .~ rect'
& L.fontSize .~ fontSize
& L.font .~ font
let renderLineNumber = renderIndexLine . createIndexLine
where
lineStyle index =
otherStyle $ if index==succ (snd $ _tasCursorPos state)
then rgbHex "#ff2200"
else rgbHex "#000000"
renderIndexLine (index,line) = drawTextLine renderer (lineStyle index) line
createIndexLine x = (x,textLine (T.pack $ show x) (35 + fromIntegral x * rH'))
when (fromMaybe False (_tacShowLineNumbers config)) $ do
drawRect renderer lineNumbersRect (Just (fromMaybe (rgbHex "#ff2200") (style ^. L.sndColor))) Nothing
drawInScissor renderer True lineNumbersRect $ do
mapM_ renderLineNumber [1..length textLines]
where
style = currentStyle wenv node
contentArea = getContentArea node style
ts = _weTimestamp wenv
offset = Point (lineNumberWidth + contentArea ^. L.x) (contentArea ^. L.y)
focused = isNodeFocused wenv node
caretTs = ts - _tasFocusStart state
caretRequired = focused && even (caretTs `div` caretMs)
caretColor = styleFontColor style
caretRect = getCaretRect config state False
selRequired = isJust (_tasSelStart state)
selColor = styleHlColor style
selRects = getSelectionRects state contentArea
Originally posted by @simmihugs in #83 (comment)
Hi! The issue of the line numbers' jittering is most likely caused by rounding. After testing for a while, I think the most reliable solution is to use drawInTranslation
instead of manually adding the offset. The resulting code would be:
...
let textLine t y = do
let wvp = fromMaybe (wenv ^. L.viewport) (removeOuterBounds style (wenv ^. L.viewport))
-- Do not use viewport's x here; just set the desired padding values
let rect' = def & L.x .~ 5 & L.y .~ y & L.w .~ 20 & L.h .~ 20
def
& L.text .~ t
& L.rect .~ rect'
& L.fontSize .~ fontSize
& L.font .~ font
let renderLineNumber = renderIndexLine . createIndexLine
where
lineStyle index =
otherStyle $ if index==succ (snd $ _tasCursorPos state)
then rgbHex "#ff2200"
else rgbHex "#000000"
renderIndexLine (index,line) = drawTextLine renderer (lineStyle index) line
createIndexLine x = (x,textLine (T.pack $ show x) (35 + fromIntegral x * rH'))
when (fromMaybe False (_tacShowLineNumbers config)) $ do
drawRect renderer lineNumbersRect (Just (fromMaybe (rgbHex "#ff2200") (style ^. L.sndColor))) Nothing
drawInScissor renderer True lineNumbersRect $ do
-- Apply x offset here
drawInTranslation renderer (Point (wvp ^. L.x) 0) $
mapM_ renderLineNumber [1..length textLines]
...
I'm not sure I understand the syntax highlighting issue; I tried switching it back and forth and I have not noticed anything strange. Let me know if it still happens how I can reproduce it (or what to look for), and I'll take a look.
Regarding using a separate repository, that's definitely the best solution. You will reduce the compile times drastically. On the other hand, I suggest you use ghcid
for development. If you modify the .ghcid file to have this content to (I only updated the name of the library):
--command "stack repl --main-is MyTextarea:exe:app"
--test ":main"
--restart=package.yaml
and run ghcid from the console (you can install it from the project's root with stack install ghcid
), you will be able to make changes and test them much more quickly. There is more information here.
Your code editing widget is looking great! This widget is so much more than textArea
and deserves its own name/package. If you happen to publish it publicly, let me know and I'll link to it from Monomer's home page.
Hi @fjvallarino,
- Linenumber jumping
I updated the repo to use your code you posted above - And It works now which is great - ghcid
I followed your recommondation and updated the ghcid file - the only reason why I'm not using ghcid all the time is because
I found the performance better withstack build
andstack exec app
- as it should be I guess because it's compiled (?) - syntax highlighting
I created a gif - for example look a line 14 the(
and the Constructor behind are effected differently by using or not using
the syntax highlighting. When using syntaxhighligthing I split each textline based on the syntax rules and draw each element
using the color from the syntaxrule and the_glpXMin
for the relating glyph. That seems to be correct for things like the
Constructors but not for the Symbols.
Generally speaking making use ofLoc
andToken
fromGHC-SyntaxHighlighter
is propably okayish for haskell code but
to make it more applicable for different languages I should change it to use some more generic data type I guess. That
would also allow for other tokens - if other languages have different tokens. - About the Widget generally speaking
I would be fine if you are interessted to include this widget in monomer directly. But of course publishing it separate from
monomer an linking it from monomer would also be an option of course. Before any of that happens I want to figure out
how to avoid the jumps in the syntax and a proper way to deal with syntax for different languages.
I've been looking into this, but I have not found a solution so far. The problem seems to arise from differences in DPI handling between NanoVG and FontManager (which is extracted from NanoVG but returns glyphs already scaled with the current DPI). In the end, it's another issue related to rounding. For example, if in your MyDrawing.hs
file you change:
let txtOrigin = Point (tx+x2) by
to:
let txtOrigin = Point (fromIntegral (floor (tx + x2))) by
you will notice some improvements, although the (%~)
in line 7 still jumps back and forth. As you expect, rendering individual glyphs or the full text-line should give the same result, and this is something the library should provide (hence, it's an issue on the library's side and not your code).
It's going to take me some time to get back to this, since I have a few other things scheduled to work on in the library. I'll let you know when I make some progress.
In the end, it was an obvious error in the initialization of Monomer's FontManager
🤦🏻♂️.
I created a PR that I will keep around for a couple of days, since I want to validate nothing looks weird.
To use it, you need to update your stack.yaml to use this commit hash:
- git: https://github.com/fjvallarino/monomer
commit: 46f22c2d621e644a573d10cfaaae10ebcec5ed0d
You will also need to make a few updates since Timestamp
is no longer an alias to Int
, but its own newtype. The timestamp related definitions should now look like this:
...
defCaretMs :: Timestamp
...
_tacCaretMs :: Maybe Timestamp,
...
instance CmbCaretMs (TextAreaCfg s e) Timestamp where
...
_tasFocusStart :: Timestamp
...
The findWidgetIdFromPath
function has been deprecated (not yet removed), so it's a good idea if you replace its usage by:
scWid = widgetIdFromPath wenv scPath
I applied your recommended changes - also pushed to the mentioned repo
But I still observe the jumping with switching on and of on the syntaxhighlighting.
That is weird. I just pulled the latest version from your repository, rebuilt it from scratch, and it works fine. Have you tried running stack purge
, then stack build
?
I changed the stack.yaml
and when stack build
but I gonna check with stack purge
right away
I run into an issue with stack purge
- on windows I get an exception which tells me I dont have permission to delete .stack-work
this still happens when I run it with an admin terminal.
I tried to delete .stack-work
by hand and rebuild - but it did only rebuild my code in the repo
Then I did some googling and used stack exec -- ghc-pkg unregister monomer
, delted the .stack-work
again and run stack build
I did not get a build error - and as timestamp
is defined in the release it supposedly used the correct version of monomer
but I still get the jumps
I tested disabling hdpi (this is not a flag exposed by Monomer's configuration, it's part of SDL initialization) and could reproduce the issue again (it didn't happen with hdpi on). It seems the difference is in a scaling factor nanovg applies to all its text-related operations. I could not find an easy way to reproduce that formula since it relies on internal state, but scaling by a largeish factor appears to work.
You will need to update your stack.yaml
again:
- git: https://github.com/fjvallarino/monomer
commit: a7f9fd904f5a9094733d5f209c79d8acef029916
Please let me know if this improves the situation for you. Thanks!
Changed to the new branch - now it works great!!!
Now I found an issue regarding the linenumbers - I have to make the linenumbers start in relation to the textArea widget not hard code like I do it right now, so still some stuff to do before I can publish it but again thanks alot for your assistance with the syntax higlighting
You need to consider the vertical position of your codeEditor
. Since you are, for the line numbers section, trying to override the offset scroll
sets, you will also need to account for the y coordinate (you already do for x). I tested now and this seems ok:
let renderLineNumber = renderIndexLine . createIndexLine
where
lineStyle index =
otherStyle $ if index==succ (snd $ _tasCursorPos state)
then rgbHex "#ff2200"
else rgbHex "#000000"
renderIndexLine (index,line) = drawTextLine renderer (lineStyle index) line
-- Modified (text starts at 1, but for calculations they run from 0 to n -1)
createIndexLine x = (x,textLine (T.pack $ show x) (fromIntegral (x - 1) * rH'))
when (fromMaybe False (_tacShowLineNumbers config)) $ do
drawRect renderer lineNumbersRect (Just (fromMaybe (rgbHex "#ff2200") (style ^. L.sndColor))) Nothing
drawInScissor renderer True lineNumbersRect $ do
-- Apply x offset here
-- Modified. Also apply y offset here. It uses the node's viewport (without subtracting padding, as contentArea does)
drawInTranslation renderer (Point (wvp ^. L.x) (node ^. L.info . L.viewport . L.y)) $
mapM_ renderLineNumber [1..length textLines]
I applied your changes, only I in the meantime added config options for colors for linenumberbackground, linenumber, current linenumber. But I think your solution is slightly of to the top so I added 3 + node ^. L.info . L.viewport . L.y
. So there might be some padding missing?
let renderLineNumber = renderIndexLine . createIndexLine
where
lineStyle index =
otherStyle $ if index==succ (snd $ _tasCursorPos state)
then fromMaybe (rgbHex "#ff2200") (_tacLineNumberNumberHighlightColor config)
else fromMaybe (rgbHex "#000000") (_tacLineNumberNumberColor config)
renderIndexLine (index,line) = drawTextLine renderer (lineStyle index) line
-- createIndexLine x = (x,textLine (T.pack $ show x) verticalPosition)
-- where
-- verticalPosition = contentArea ^. L.y - 2 - 25 + fromIntegral x * rH'
createIndexLine x = (x,textLine (T.pack $ show x) (fromIntegral (x - 1) * rH'))
when (fromMaybe False (_tacShowLineNumbers config)) $ do
drawRect renderer lineNumbersRect
(Just (fromMaybe (rgbHex "#11ff00") (_tacLineNumberBackgroundColor config))) Nothing
drawInScissor renderer True lineNumbersRect $ do
-- Apply x offset here
-- Modified. Also apply y offset here. It uses the node's viewport
-- (without subtracting padding, as contentArea does)
--drawInTranslation renderer (Point (wvp ^. L.x) 0) $
drawInTranslation renderer (Point (wvp ^. L.x) (3 + node ^. L.info . L.viewport . L.y)) $
mapM_ renderLineNumber [1..length textLines]
I changed the name of the https://github.com/simmihugs/codeEditor to codeEditor
and added a library to the package. I allready tried to use the library in another project which works.
That's strange: it looks off if I add the +3
you mention. In my case, the numbers look aligned with the text, although not with respect to the highlight box.
I allready tried to use the library in another project which works.
Nice!
The PR for the glyph positioning issue has been merged to main, making the previous commit hash invalid.
You can use this in stack.yaml
until 1.4 is released:
- git: https://github.com/fjvallarino/monomer
commit: b80e2ca0360b5d740c9e1e9e8a07e00457744f6b
Once you update, the build of your component will fail because of a change where the Timestamp
type was renamed to Millisecond
. If you search and replace in your code, it should compile fine.
I found out that the offset is nessecary when a larger font size is used (compare screenshots below)
Next I will update to the new release
What I get - on the left with the offset* on the right without
That the numbers without the offset are very slightly to much to the top
But then I tried it with a different font size. Again with offset left, without right ... and now the one without is correct and
the one with is false.
And compared with different fontsize but in both cases with the 3 +
- With offset I mean the
3 +
I added
Sorry, I missed this. I sent a PR to your project with a possible fix: simmihugs/codeEditor#1.
I think the complication you were having was caused by generating the text positions manually. The fix does two things:
- Reuses the bounding boxes of the text lines.
- Considers the descending of the font.
I tried with a few font sizes (small and really large) and it looks good for me.
Looks great I merged the request - it looks great.
Great!
I'm not sure how you handle the pull requests you receive, but you can test them locally before merging them to your repository to ensure the changes work as expected. This page shows how you can check out a PR. I don't personally use GitHub's CLI, so I follow the (cumbersome) steps listed in "Modifying an inactive pull request locally" (without the push part).
I'll close the issue now. Please re-open or create a new one if needed. Thanks!