Giter VIP home page Giter VIP logo

chlorophyll's Introduction

Hello, I'm rdbende ๐Ÿ‘‹

  • ๐Ÿ Most of the time I'm programming in Python. I like making GUIs with GTK4 and sometimes with Tkinter.
  • ๐Ÿ˜ป You might wanna check out my themes for Tkinter. The Sun Valley ttk theme can even make it look like Windows 11!
  • ๐Ÿฆ€ Lately I've been into GUI development using gtk-rs
  • ๐ŸŽน I'm also interested in sound engineering, synths and electronic music, you can find some related projects on my profile
  • ๐Ÿง My distro of choice is Fedora Silverblue ๐Ÿ‘ฃ
  • ๐Ÿ˜ If you're interested in what I'm working on at the moment, check out my Mastodon profile
  • ๐Ÿ“ซ Feel free to contact me via email, or chat with me on Matrix

chlorophyll's People

Contributors

billyeatcookies avatar littlewhitecloud avatar moosems avatar rdbende avatar sidmaz666 avatar whmsft avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

chlorophyll's Issues

Highlighting issues.... still

The following doesn't highlight properly:

from chlorophyll import CodeView
from pygments import lexers
from tkinter import Tk

root = Tk()
cd = CodeView(root, lexer=lexers.PythonLexer)
cd.pack()
cd.insert("insert", "from\nfrom\nfrom")
root.mainloop()

Lambda crash

from chlorophyll import CodeView


from tkinter import Tk
root = Tk()
text = CodeView(root)
text.pack()
def test():
    text.insert("end", "\n"*2)
    root.destroy()
    # Using after_idle still causes an error
    # The error caused by after_idle is exclusive to Mac OS
    # Error: objc[number]: Invalid or prematurely-freed autorelease pool hex_value.
    # root.after_idle(root.destroy)

root.after(10, test)
# root.after_idle(test)
# Using after_idle still causes an error when used with test()
# It causes a corrupted memory error
"""
invalid command name "4532481984<lambda>"
    while executing
"4532481984<lambda>"
    ("after" script)
objc[27242]: autorelease pool page 0x7fe67c93d000 corrupted
  magic     0x00000000 0x00000000 0x00000000 0x00000000
  should be 0xa1a1a1a1 0x4f545541 0x454c4552 0x21455341
  pthread   0x1170bd5c0
  should be 0x1170bd5c0
"""
root.mainloop()


root = Tk()
root.after(10, root.destroy)
root.mainloop()

Inserting can cause weird highlighting

If you insert instead of copy paste a large snippet of code it is likely to highlight weirdly. Reproducible code:

from tkinter import Tk
from chlorophyll import CodeView

root = Tk()
text = CodeView(root, wrap="none")
text.pack()

text.insert("1.0", """
from tkinter import Tk
from chlorophyll import CodeView

root = Tk()
text = CodeView(root, wrap="none")
text.pack()

text.insert("1.0", "Hello world")

root.mainloop()
""")

root.mainloop()

Add builtin linenumbers

@Moosems:

Find and replace, linenumbers, and a minimap could be based off of Porcupine by Akuli on GitHub. If this is meant to be an all builtin code editor than this would be a sensible addition

@rdbende:

I plan to make linenumbers feature, but I think that the find and replace and the minimap are outside the scope of this project, and if someone needs such features, they would rather use they own than the builtin one ๐Ÿ˜. This widget isn't meant to be an all builtin code editor, just a simple CodeView widget

@Moosems:

Understood :)

@rdbende I made a pretty good linebar in DIP if you want to steal it

@rdbende I released a good linebar widget on pypi :)

Would we like to add the linebar widget?

Emojis and some weird chars are broken

@Moosems:

It works in normal Text pretty well (although with its own issues)but doesn't in chlorophyll

@rdbende:

Can you provide an example?
In general, Tkinter (Tk) doesn't support most emojis (any character that's beyond U+FFFF, to be precise), so I'm wondering what is what works with the normal text widget but not with Chlorophyll

@Moosems:

Tkinter recently made them not break widgets so example chars are:

๐Ÿ˜€ ๐Ÿ˜ƒ ๐Ÿ˜„ ๐Ÿ˜ ๐Ÿ˜† ๐Ÿ˜… ๐Ÿ˜‚ ๐Ÿคฃ ๐Ÿ˜Š ๐Ÿ˜‡ ๐Ÿ™‚ ๐Ÿ™ƒ ๐Ÿ˜‰ ๐Ÿ˜Œ

Instructions: Paste and then press backspace

@rdbende:

simplescreenrecorder-2022-08-01_21.49.24.mp4

@Moosems:

Very odd... honestly I wish tkinter could support it cause some people use emojis in code and if it doesn't work I'm sure I'm gonna hear about it a lot later on

@rdbende It happens in mantaray too though so it seems less like a pygments thing

Happens in Porcupine too, probably due to some Pygments coloring tag that confuses Tk

V.0.4.0 Still has the Lexer issue ๐Ÿ˜•

Traceback (most recent call last):
  File "/Users/Moosems/Desktop/DIP-dev/DIP.py", line 12, in <module>
    from DIP_module.editor import Editor
  File "/Users/Moosems/Desktop/DIP-dev/DIP_module/editor.py", line 9, in <module>
    from chlorophyll import CodeView
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/chlorophyll/__init__.py", line 1, in <module>
    from .codeview import CodeView
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/chlorophyll/codeview.py", line 20, in <module>
    LexerType = Union[Type[pygments.lexer.Lexer], pygments.lexer.Lexer]
                           ^^^^^^^^^^^^^^
AttributeError: module 'pygments' has no attribute 'lexer'. Did you mean: 'lexers'?

`_copy()` uses `tkinter`'s builtin copy function

This is an issue because the clipboard in tkinter is deleted and emptied when the program is closed and this has been an issue for me in the past. I recommend we use pyperclip's copy function as it is battle proven to work and not very memory intensive.

Lambda crash

from chlorophyll import CodeView


from tkinter import Tk
root = Tk()
text = CodeView(root)
text.pack()
def test():
    text.insert("end", "\n"*2)
    root.destroy()
    # Using after_idle still causes an error
    # The error caused by after_idle is exclusive to Mac OS
    # Error: objc[number]: Invalid or prematurely-freed autorelease pool hex_value.
    # root.after_idle(root.destroy)

root.after(10, test)
# root.after_idle(test)
# Using after_idle still causes an error when used with test()
# It causes a corrupted memory error
"""
invalid command name "4532481984<lambda>"
    while executing
"4532481984<lambda>"
    ("after" script)
objc[27242]: autorelease pool page 0x7fe67c93d000 corrupted
  magic     0x00000000 0x00000000 0x00000000 0x00000000
  should be 0xa1a1a1a1 0x4f545541 0x454c4552 0x21455341
  pthread   0x1170bd5c0
  should be 0x1170bd5c0
"""
root.mainloop()


root = Tk()
root.after(10, root.destroy)
root.mainloop()

Add all tokens

Currently is missing a few pygments tokens like Generic, Text, String.Backtick, and a few more.

Linenumbers recieved new update

The linenumbers module just got an update today that is not backwards compatible. This means some stuff will have to change.

Add `.highligh_area()` method

I believe a .highlight_area() method should be added with a start and end line index given as arguments. @rdbende Are you ok with this? The issue is the only way to currently do this is to move around the insert a ton and then use .highlight() but it would be so much cleaner and nicer to use .highlight_area(). You might ask: "why?". Sometimes when you make a ton of inserts really fast in a method it forgets to actually highlight the area so it would be good to either:
A). Have a patch fixing the bug
or
B). Just allowing a certain area to be re-highlighted (may help fix the docstring issue)

Use of configure results in error

When I do something like:

def open_file(filename): lexer = pygments.lexers.get_lexer_for_filename(filename) code_view.configure(lexer=lexer)
I get the following error:

File "/home/user/.cache/pypoetry/virtualenvs/qdnote-xQFX2N3W-py3.10/lib/python3.10/site-packages/chlorophyll/codeview.py", line 217, in configure
self._set_lexer(lexer)
File "/home/user/.cache/pypoetry/virtualenvs/qdnote-xQFX2N3W-py3.10/lib/python3.10/site-packages/chlorophyll/codeview.py", line 204, in _set_lexer
self.highlight_all()
File "/home/user/.cache/pypoetry/virtualenvs/qdnote-xQFX2N3W-py3.10/lib/python3.10/site-packages/chlorophyll/codeview.py", line 165, in highlight_all
for token, text in lex(lines, self._lexer()):
TypeError: 'PythonLexer' object is not callable

Use tree-sitter

[15:00]	Moosems	Actually not :)
[15:00]	Moosems	His chlorophyll package
[15:00]	Arrinao	oh she did
[15:01]	Moosems	https://github.com/rdbende/chlorophyll/pull/23
[15:02]	Akuli	porcupine has one of these too
[15:03]	Moosems	Yep
[15:04]	Akuli	in porcupine, the first step is to turn all changes in the text widget into nice Change objects: https://github.com/Akuli/porcupine/blob/main/porcupine/textutils.py#L22-L60
[15:04]	Akuli	e.g. Change(start=[1, 0], old_end=[1, 5], new_end=[1, 4], old_text='hello', new_text='toot') means replacing 'hello' with 'toot' at the start of the file
[15:04]	Moosems	Yep
[15:04]	Akuli	the code that constructs these is puke :D
[15:05]	Akuli	it basically has to implement everything that the insert, delete and replace manual pages describe
[15:07]	Akuli	it seems to assume that you can just start lexing anywhere you want, at the start of any line?
[15:07]	Moosems	?
[15:07]	Akuli	it basically puts self.get(f"{start_line}.0", f"{end_line}.end") to pygments.lex()
[15:08]	Moosems	Yes, it only lexes what it needs to
[15:08]	Akuli	that won't work when you have a multiline string
[15:08]	Moosems	Already an issue I opened :)
[15:08]	Moosems	Which is what highlight_area() will help solve
[15:09]	Akuli	i have already "solved" it in porcupine about a year ago
[15:09]	Akuli	i say "solved" because it turned out to be a really hard problem
[15:09]	Moosems	By finding out when they start and end?
[15:09]	Akuli	i wanted it to work for all languages that pygments supports, so hard-coding something for python wasn't a solution
[15:09]	Akuli	this also applies to e.g. multiline comments in c
[15:10]	Moosems	Thats why I think the user should add multi line strings and docstrings as a parameter
[15:10]	Moosems	Like in DIP
[15:10]	Akuli	the best you can do (as far as i can tell): figure out when the lexer's internal state is same as its starting state, and mark those places: you "can" start lexing again from any one of them
[15:10]	Moosems	And if its an empty string it assumes theres no docstring type
[15:10]	biberao	Akuli: https://www.youtube.com/watch?v=WpAY8TGt2Ks
[15:10]	Akuli	i say "can" because even that doesn't work in all cases
[15:11]	Moosems	It's a copmlicated issue
[15:11]	Akuli	yes :)
[15:13]	Moosems	I wonder how VS Code does it
[15:13]	Akuli	to me the solution was to switch to a different highlighting library: tree-sitter
[15:13]	Moosems	tree-sitter?
[15:13]	Akuli	with tree-sitter you say "text from 12.34 to 56.78 was previously 'blah blah' but it is now 'blah blah'"
[15:14]	Akuli	it is designed to be used in an editor, unlike pygments
[15:15]	Moosems	How do I use it? Could you help make an MRE?
[15:15]	Akuli	sorry, i'm not going to spoon-feed you something that took me days to figure out
[15:15]	Moosems	XD
[15:16]	Akuli	it's not very straight forward because tree-sitter isn't a library just for syntax highlighting, it gives you a parse tree that contains things like "function definition" instead of things like "the def keyword"
[15:16]	Moosems	I noticed
[15:16]	Akuli	then it's your job to turn that syntax tree into whatever you want
[15:16]	Akuli	in porcupine i set up yaml files that describe how to do this, and there's one for every tree-sitter highlighted language
[15:17]	Akuli	note that porcupine still supports highlighting with pygments, it just isn't the default in e.g. python
[15:17]	Moosems	It doesn't have nearly as many languages
[15:17]	Akuli	yeah
[15:17]	Akuli	in porcupine the idea is to use tree-sitter most of the time, and fall back to pygments when a user has an exotic language
[15:18]	Akuli	this works well because your files are typically small and somewhat simple when you work in an exotic language, for any "real work" you tend to use a popular language instead
[15:19]	Moosems	Care if we steal the idea?
[15:19]	Akuli	go ahead :D
[15:19]	Akuli	you can take all the code if you want, of course :)

Trying to undo with nothing left in stack causes error and closes app

File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/chlorophyll/codeview.py", line 100, in _cmd_proxy
    raise e from None
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/chlorophyll/codeview.py", line 96, in _cmd_proxy
    result = self.tk.call(cmd)
_tkinter.TclError: nothing to undo

Inserted strings don't get highlighted

Test:

from chlorophyll import CodeView
from tkinter import Tk
from pygments.lexers import PythonLexer

root = Tk()
codeview = CodeView(root, lexer=PythonLexer)
codeview.pack()

for i in range(100):
    codeview.insert("end", "print()\n")

root.mainloop()

Serious performance issues

@Moosems:

As mentioned previously the CodeView widget can be quite slow and upon further testing there are some serious performance issues to be addressed. The code below makes a very slow CodeView widget after inserting 100 long lines of highlightable chars. To truly understand how slow it goes you must scroll around. Test code used:

from tkinter import Tk, Text, Scrollbar
from chlorophyll import CodeView
import time
time_reg = []
time_CodeView = []
def perform_test(msg, master, text_widget):
    start_time = time.time()
    for i in range(len((current_text := text_widget.get("1.0", "end").splitlines()))):
        for j in range(len(current_text[i])): text_widget.see(f"{i+1}.{j}")
    for i in range(1000): master.geometry(f"{i+1}x{i+1}")
    end_time = time.time()
    print(f"Time taken for {msg} to scroll down and resize: {(total_time := end_time-start_time)}")
    if msg == "Text": time_reg.append(total_time)
    else: time_CodeView.append(total_time)
    master.destroy()

def testRegText():
    root = Tk()
    text = Text(root, wrap="none")
    text.grid(row=0, column=0)
    xscrollbar = Scrollbar(root, orient="horizontal", command=text.xview)
    xscrollbar.grid(row=1, column=0, sticky="ew")
    text.config(xscrollcommand=xscrollbar.set)
    yscrollbar = Scrollbar(root, orient="vertical", command=text.yview)
    yscrollbar.grid(row=0, column=1, sticky="ns")
    text.config(yscrollcommand=yscrollbar.set)
    text.insert("1.0", "|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|\n"*200)
    root.after(1000, lambda: perform_test("Text", root, text))
    root.mainloop()

def testCodeView():
    root = Tk()
    text = CodeView(root, wrap="none")
    text.pack()
    text.insert("1.0", "|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|โ€…|\n"*200)
    root.after(1000, lambda: perform_test("CodeView", root, text))
    root.mainloop()

testRegText()
testCodeView()
test_count = 5
for i in range(test_count):
    print(f"Test {i+1} of {test_count}")
    testRegText()
    testCodeView()

print("Average time taken for Text to scroll down and resize:", sum(time_reg)/len(time_reg))
print("Average time taken for CodeView to scroll down and resize:", sum(time_CodeView)/len(time_CodeView))

Output:

Time taken for Text to scroll down and resize: 3.411916971206665
Time taken for CodeView to scroll down and resize: 82.07269406318665
Test 1 of 5
Time taken for Text to scroll down and resize: 3.085193157196045
Time taken for CodeView to scroll down and resize: 82.81531071662903
Test 2 of 5
Time taken for Text to scroll down and resize: 3.0634210109710693
Time taken for CodeView to scroll down and resize: 81.8645350933075
Test 3 of 5
Time taken for Text to scroll down and resize: 3.0675089359283447
Time taken for CodeView to scroll down and resize: 82.0897867679596
Test 4 of 5
Time taken for Text to scroll down and resize: 3.0569241046905518
Time taken for CodeView to scroll down and resize: 81.81994986534119
Test 5 of 5
Time taken for Text to scroll down and resize: 3.06306791305542
Time taken for CodeView to scroll down and resize: 81.95325803756714
Average time taken for Text to scroll down and resize: 3.124672015508016
Average time taken for CodeView to scroll down and resize: 82.132455301284794

@rdbende:

Apparently, if you don't have a space between the |-s, they're about the same in time. Hmmm...

@Moosems:

Hmmm

Changing it out with other chars does change speed though. I think that this is something important to look into

Highlight Only Visible Area (performance improvement)

This will fix #9 and will fix #11. The idea? Highlight only text that's visible. This will be done by changing over to a method called .highlight() that will highlight all the visible text with reference to all the text in the widget. The structure of the method will be roughly as follows:

  1. Get the visible area
  2. Remove all tags
  3. Get all the text in the widget
  4. Lex the lines and use offsets
  5. Find what is not visible at all and discard those
  6. Modify remaining tags to cut out parts outside of view area
  7. Apply what's left

@rdbende What do you think? I have a feeling this will have a performance improvement for large amounts of code and and that small amounts will still be fine.

New lexer code crashes

from tkinter import Tk

from pygments.lexers import PythonLexer

from chlorophyll import CodeView

root = Tk()
cd = CodeView(root, lexer=PythonLexer)
cd.pack(fill='both', expand=True)
root.mainloop()
Traceback (most recent call last):
  File "/Users/Moosems/Desktop/chlorophyll/test.py", line 5, in <module>
    from chlorophyll import CodeView
  File "/Users/Moosems/Desktop/chlorophyll/chlorophyll/__init__.py", line 1, in <module>
    from .codeview import CodeView
  File "/Users/Moosems/Desktop/chlorophyll/chlorophyll/codeview.py", line 20, in <module>
    LexerType = Union[Type[pygments.lexers.Lexer], pygments.lexers.Lexer]
                           ^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/pygments/lexers/__init__.py", line 338, in __getattr__
    raise AttributeError(name)
AttributeError: Lexer

Performance improvement suggestion

@Moosems:

@rdbende I think you might want to consider this. Currently chlorophyll highlights the entire text box. For small amounts of code of moderately sized snippets this works fine but with large amounts of code it grinds to a halt. My suggestion is to only highlight what's in the current view and remove the other tags to save speed. The locations of the other tags can be kept in a list but by not highlighting them all at the same time it saves a ton of time. As the Tcl Core itself has noted tags are very slow so minimizing the amount of active tags is very important.

Sample code to test with:

from tkinter import Tk
from chlorophyll import CodeView
root = Tk()
text = CodeView(root)
text.pack()
text.insert("end", "text = BaseText(root, lexer=lexers.PythonLexer, undo=True, font=(\"Courier New bold\", 15), indentation_type=\"space\", indentation_amount=4, bind_string=\"Command\", comment_type=(\"# \", \"\\\"\\\"\\\"\"))\n"*100)
root.mainloop()

@rdbende:

I'm afraid it wouldn't make it much faster, but would make it much more laggy, because it has to constantly highlight everything.

@Moosems:

Why do you say it wouldn't get much faster? And for highlighting everything that's the issue I am trying to avoid by not using as many tags

@rdbende:

Because it has to constantly re-add the tags, as you scroll. The same thing happens in Porcupine, so I know, it's laggy, and doesn't really reduce CPU usage.

@Moosems:

Not if it doesn't move outside the view

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.