Tkinter/Pygments: Söz Dizimi Renklendirmesi

Merhaba.

Tkinter ve Pygments kullanarak basit bir metin düzenleyicisi yapmaya çalışırken, henüz çözümünü bulamadığım beklenmeyen bir durumla karşılaştım. Programdan yapmasını beklediğim, yazdığım yazılar şayet Python söz dizimine uyuyorsa bu yazıları renklendirmek. Aslında programın tokenleri ayırt edip token tipine göre renklendirmesinde sorun yok. Ancak bazı durumlarda istenmeyen sonuçlarla karşılaşıyorum.

Örneğin:
2018-06-10%2023-52-46%20ekran%20g%C3%B6r%C3%BCnt%C3%BCs%C3%BC
yukarıdaki gibi yazdığımda, a değişkeni de string “a” değişkeninin rengine bürünüyor. Veya bazı durumlarda renk değişikliği gerçekleşmiyor.
Ve herhangi bir script’teki tüm kodları pencere içine aktardığımda da istenmeyen sonuçlar oluşuyor.
2018-06-11%2000-05-55%20ekran%20g%C3%B6r%C3%BCnt%C3%BCs%C3%BC
Yani bir harf grubu (örneğin ‘if’) renklendirilince, metinde if içeren bütün sözcüklerin if kısmı renklenmeye başlıyor.

Acaba bu sorunu çözebilmek için nasıl bir yol izlememi önerirsiniz?

Kodlar:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from pygments import lex
from pygments.token import Token
from pygments.lexers import Python3Lexer

try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk

ROOT = tk.Tk()
TEXT = tk.Text(master=ROOT, fg="white", bg="black", font="TkDefaultFont 10")
TEXT.pack(fill="both", expand=True)


def tag(event):

    def colorize(word, color):
        index = []
        index1 = TEXT.search(word, "1.0", "end")
        while index1:
            index2 = ".".join([index1.split(".")[0], str(int(index1.split(".")[1]) + len(word))])
            index.append((index1, index2))
            index1 = TEXT.search(word, index2, "end")
        for i, j in index:
            TEXT.tag_add(word, i, j)
            TEXT.tag_configure(word, foreground=color)

    for token, content in lex(TEXT.get("1.0", "end"), Python3Lexer()):
        if token == Token.Literal.Number.Integer:
            colorize(content, color="purple")
        elif token == Token.Keyword:
            colorize(content, color="orange")
        elif token == Token.Operator.Word:
            colorize(content, color="red")
        elif token == Token.Name.Builtin:
            colorize(content, color="blue")
        elif token == Token.Comment.Hashbang or token == Token.Comment.Single:
            colorize(content, color="grey")
        elif token == Token.Keyword.Namespace:
            colorize(content, color="yellow")
        elif token == Token.Namespace:
            colorize(content, color="green")
        elif token == Token.Punctuation:
            colorize(content, color="brown")
        elif token == Token.Literal.String.Double:
            colorize(content, color="cyan")


TEXT.bind("<KeyRelease>", tag)
ROOT.mainloop()

Merhaba,

Ben de bir aralar benzer bir proje ile uğraşıyordum. Syntax highlighting olayı için örnek araştırırken CodePad adlı bir projeye denk geldim. İncelerseniz, nasıl yapabileceğinizi anlarsınız diye düşünüyorum.

Token’lara bölme işleminde sorun var. İzleyebileceğiniz 3 yol var:

  1. Token’lara bölme işlemini siz yaparsınız. Eğer bu yolu izleyecekseniz, öncelikle bir lexical analyzer’ın nasıl yazılabileceğini araştırmakla başlamanızı tavsiye ederim. Hazır bir araç olarak Flex’i kullanabilirsiniz. Flex’te C ve C++ programlama dillerini kullanmanız gerek ancak diğer programlama dilleri için de benzer araçlar bulabilirsiniz.
  2. Eğer editörünüz sadece Python içinse standart kütüphanedeki tokenize modülünü kullanabilirsiniz.
  3. Eğer editörünüz birden fazla programlama dilini destekleyecekse, bu işi Pygments’e bırakmak mantıklı olabilir. Pygments’i Tkinter’a entegre etmek için yeni bir formatter yazmalısınız.

Öğrenme amaçlı ilkini Python üzerinden yapabilirsiniz. Diğer durumlar için, desteğinize bağlı olarak, 2. ve 3. yolları öneririm.

1 Beğeni

Demek siz blackvkng rumuzlu arkadaşsınız? Şayet oysanız sizin projenize bakmıştım daha önce. Hatta o projeyi aradım ama bulamadım. Linkini verdiğiniz diğer projeye biraz göz atmıştım. Ancak öyle derinlemesine incelemedim daha.

Daha önce C/C++ hiç kullanmadım. Diğer programlama dilleri için de benzer bir araç varsa da henüz araştırmadım. Bu bir seçenek olarak dursun bir kenarda.

Editör sadece Python söz dizimini desteklemeyebilir. Ama tokenize modülünün kullanımını öğrenmek için sadece Python söz dizimini destekleyen bir uygulama yapmayı düşünüyorum. Standart kütüphanedeki açıklamalara biraz bakmıştım ama genellikle komut satırından bir py dosyasındaki karakterleri ayrıştırmakla alakalı örnekler görmüştüm. Biraz daha ayrıntılı araştırmam gerekecek sanırım.

Pygments ile yeni bir formatter oluşturmak için de biraz alıştırma yapmam gerekiyor, çünkü şu an nasıl yapıldığını bilmiyorum.

Bu üç yolu da zaman içerisinde araştıracağım. Ama önce pygments’te yeni bir formatter nasıl oluşturulur ona bakarım herhalde. Teşekkür ederim.

1 Beğeni

Birçok kere rumuz değiştirdim. Projeyi buraya yükledim. OOP’un hakkını tam verememiş olabilirim, biraz rahatsız edici duruyor kodlar :smiley:

1 Beğeni

Hocam bu parametre olarak verdiginiz keywordsleri normal olarak degil de baska turlu verin(cok sacma cumle oldu, kabul ediyorum.Ama anlayacaksınız.)

Hocam şu projeyi inceler misin?

Hocam ben pygments le renklendirme yapmaya calıstım,ama pyqt ye entegre edemedim pygments i(belki de benim beceriksizligimdir.)

Ben tkinter yerine pyqt kullandim ve pyqt de QSyntaxHighlighter ı kullandım,renklendirmede o kadar sıkıntı cıkarmıyor,bence pyqt kullanın bu anlamda,tkinterle yapmak istiyorsanız da siz bilirsiniz.Ama:

atıyorum bir keywords regexi yazacaksınız

"int"
yerine
"\\bint\\b"
regexi kullanın.

siz editorde int i sarı gostermek istiyorsunuz,editore int yazdıgınızda

intint ,intrazonal vb yazdıgınızda kelime icindeki intler sarı olmaz,ama

#int vb yazdıgınızda sadece int yazısı sarı olur.Bu arada attıgım kendi projemde ufak bir bug
tespit ettim bugun duzeltirim.

Kolay gelsin :slight_smile:

1 Beğeni

Colorize fonksiyonunu dışarıda tanımlasanız daha verimli olur. Çünkü tag fonksiyonu her çağrıldığında tekrar tanımlanıyor.

1 Beğeni

Hepinize çok teşekkür ederim arkadaşlar.
Sanırım aşağıdaki örnek daha iyi oldu gibi.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
if sys.version_info.major == 2:
    exit()
elif sys.version_info.major == 3:
    import builtins as builtins
    import tkinter as tk
import io
import tokenize
import keyword


root = tk.Tk()
text = tk.Text(master=root, fg="white", bg="black", font="TkDefaultFont 10")
text.pack(fill="both", expand=True)

count = 0


def colorize(*args):
    global count
    row1, col1 = args[0].start
    row1, col1 = str(row1), str(col1)
    row2, col2 = args[0].end
    row2, col2 = str(row2), str(col2)
    start = ".".join((row1, col1))
    end = ".".join((row2, col2))
    text.tag_add(str(count), start, end)
    try:
        text.tag_config(str(count), foreground=args[1], font=args[2])
    except IndexError:
        text.tag_config(str(count), foreground=args[1])
    count += 1


def search(event):
    try:
        for i in tokenize.tokenize(io.BytesIO(text.get("1.0", "end").encode("utf-8")).readline):
            if i.type == 1:
                if i.string in keyword.kwlist:
                    colorize(i, "orange")
                elif i.string in dir(builtins):
                    colorize(i, "blue")
                else:
                    colorize(i, "white")
            elif i.type == 2:
                colorize(i, "cyan")
            elif i.type == 3:
                colorize(i, "purple")
            elif i.type == 53:
                if i.string == "," or i.string == "." or i.string == ":":
                    colorize(i, "orange")
                elif i.string == "(" or i.string == ")" or i.string == "[" \
                        or i.string == "]" or i.string == "{" or i.string == "}":
                    colorize(i, "darkred")
                else:
                    colorize(i, "green")
            elif i.type == 57:
                colorize(i, "grey", "TkDefaultFont 10 italic")
    except tokenize.TokenError:
        pass


text.bind("<KeyRelease>", search)
root.mainloop()

2018-06-12%2006-27-15%20ekran%20g%C3%B6r%C3%BCnt%C3%BCs%C3%BC

2 Beğeni

Merhaba, yeni bir sorunla daha karşınızdayım. :slight_smile:

Yukarıdaki kodlarda ufak bir değişiklik yaptım.

Bu değişikliği yapma amacım, text widgeti içinde bir tuşa basmadan önce, text widgetine aktarılan kodların renklenmesini sağlamak, yukarıdaki kodlar bu renklendirme işlemini, kullanıcı text widgetine birşeyler yazınca gerçekleştiriyordu, şimdi ise kodlar text widgetine aktarılır aktarılmaz renklendiriliyorlar.

Ama renklendirme işlemi biraz gecikiyor. Bir önceki mesajımdaki kodları çalıştırdığımda gecikme daha fazlaydı, üstelik yazıyı yazarken de gecikmeler oluyordu. Aşağıdaki kodları çalıştırdığımda ise renklendirme işleminin gecikmesi daha az ve yazıyı yazarken gecikme de olmuyor. Ama yine de her iki programda da renklendirmeyle alakalı bir gecikme söz konusu.

Acaba bu gecikme sorununu giderebilmek için ne yapmamı önerirsiniz?

Kodlar:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
if sys.version_info.major == 2:
    exit()
elif sys.version_info.major == 3:
    import tkinter as tk
    import builtins as builtins
import io
import keyword
import tokenize
import threading

root = tk.Tk()
text = tk.Text(master=root, fg="white", bg="black", font="TkDefaultFont 10")
text.pack(fill="both", expand=True)

count = 0


def colorize(*args):
    global count
    row1, col1 = args[0].start
    row1, col1 = str(row1), str(col1)
    row2, col2 = args[0].end
    row2, col2 = str(row2), str(col2)
    start = ".".join((row1, col1))
    end = ".".join((row2, col2))
    text.tag_add(str(count), start, end)
    try:
        text.tag_config(str(count), foreground=args[1], font=args[2])
    except IndexError:
        text.tag_config(str(count), foreground=args[1])
    count += 1


def search():
    while True:
        try:
            for i in tokenize.tokenize(io.BytesIO(text.get("1.0", "end").encode("utf-8")).readline):
                if i.type == 1:
                    if i.string in keyword.kwlist:
                        colorize(i, "orange")
                    elif i.string in dir(builtins):
                        colorize(i, "blue")
                    else:
                        colorize(i, "white")
                elif i.type == 2:
                    colorize(i, "cyan")
                elif i.type == 3:
                    colorize(i, "purple")
                elif i.type == 53:
                    if i.string == "," or i.string == "." or i.string == ":":
                        colorize(i, "orange")
                    elif i.string == "(" or i.string == ")" or i.string == "[" \
                            or i.string == "]" or i.string == "{" or i.string == "}":
                        colorize(i, "darkred")
                    else:
                        colorize(i, "green")
                elif i.type == 57:
                    colorize(i, "grey", "TkDefaultFont 10 italic")
        except tokenize.TokenError:
            pass


thread = threading.Thread(target=search)
thread.daemon = True
thread.start()
thread.join(1)
root.mainloop()

Hocam saçma ama aklıma geldi.

Siz baştan bütün tuşlara basıldıgında yapılacak bir iş ayarlarsınız.Yani kullanıcı belli bir tuşa bastıgında metin mutlaka degişmiş olur.O zaman da renklendirme fonksiyonunuzu çalıştırırsınız.

Yani renklendirme fonksiyonu her zaman degil, kullanıcı tuşa bastıgında çalıssın.
İnsallah anlatabilmişimdir.

Ben bu yavaşlıgın nedeninin search fonksiyonunun en başındaki while oldugunu dişünüyorum.Bu yuzden thread çaliştirilmiş,

Benim dedigimi uygulayabilirseniz belki thread kullanmaya bile gerek kalmaz.

Kolay gelsin :slight_smile:

Ya da on_text_changed vb bir metod varsa o araştırılmalı.

Kolay gelsin :slight_smile:

Zaten öyleydi, bir önceki mesajımda paylaştığım program sizin dediğiniz gibi çalışıyordu.

Bir önceki mesajdaki programda while yoktu, sizin dediğiniz gibi search fonksiyonu sadece tuşlara basılınca çalışıyordu. Ama o program, while ve threading kullanılarak oluşturulmuş programdan daha geç yazıları renklendiriyor.

1 Beğeni

Demek ki yanlış düşünüyormuşum.Çok pardon.Kolay gelsin.

Estafurullah, belki de daha farklı bir yol izlemeliyim. Yani her iki program da renklendirme hızı açısından beni pek tatmin etmedi. Başka bir yol denemek lazım. Teşekkür ederim.

http://code.activestate.com/recipes/464635-call-a-callback-when-a-tkintertext-is-modified/

Hocam biraz ustunkoru araştırdım ama vaktim olsa hocam seve seve araştırayım.

Ama:
Onceki kodlarınızda

Yerine

text.bind("<Modified>", search)
denediniz mi?(muhtemelen denemişsinizdir)

Kolay gelsin :slight_smile:

Yok denemedim, hemen deniyorum.

Şöyle bir hata verdi:

_tkinter.TclError: bad event type or keysym "Modified"

Sanırım şöyle olmalı:

text.bind("<<Modified>>", search)

Edit: İlginçtir widgete yazı yazarken yazılar renklendirilmiyor ama bir scriptteki kodları widgete aktarınca yazılar renklendiriliyor ama bu sefer de widgete daha sonradan eklemek istediğimiz yazılar renklendirilmiyor.

Bu yavaşlık sorunuyla başka bir editörde karşılaşmıştım. Renklendirme işi, doğası gereği, yavaş çalışmaya meyilli bir iş. Belki her karakter girişinde renklendirme yapmak yerine sadece değiştirilen satırda renklendirme yapılabilir. Bunun dezavantajı olur mu bilemem, olursa da ele alınması gerek.

Acaba bu değiştirilen satırda renklendirme işlemi nasıl yapılır biraz çalışayım dedim de, garip bir davranışla karşılaştım.

Kodlar:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
if sys.version_info.major == 2:
    exit()
elif sys.version_info.major == 3:
    import io
    import keyword
    import builtins
    import tokenize
    import threading
    import tkinter as tk


root = tk.Tk()
text = tk.Text(master=root, fg="white", bg="black", font="TkDefaultFont 10")
text.pack(fill="both", expand=True)

count = 0


def colorize(*args):
    global count
    row1, col1 = args[0].start
    start = str(row1) + "." + str(col1)
    row2, col2 = args[0].end
    end = str(row2) + "." + str(col2)
    text.tag_add(str(count), start, end)
    try:
        text.tag_config(str(count), foreground=args[1], font=args[2])
    except IndexError:
        text.tag_config(str(count), foreground=args[1])
    count += 1


def search(event):
    index = text.index("insert")
    try:
        for i in tokenize.tokenize(io.BytesIO(text.get("{}.0".format(index[0]), "end").encode("utf-8")).readline):
            if i.type == 1:
                if i.string in keyword.kwlist:
                    colorize(i, "orange")
                elif i.string in dir(builtins):
                    colorize(i, "blue")
                else:
                    colorize(i, "white")
            elif i.type == 2:
                colorize(i, "cyan")
            elif i.type == 3:
                colorize(i, "purple")
            elif i.type == 53:
                if i.string == "," or i.string == "." or i.string == ":":
                    colorize(i, "orange")
                elif i.string == "(" or i.string == ")" or i.string == "[" \
                        or i.string == "]" or i.string == "{" or i.string == "}":
                    colorize(i, "darkred")
                else:
                    colorize(i, "green")
            elif i.type == 57:
                colorize(i, "grey", "TkDefaultFont 10 italic")
    except tokenize.TokenError:
        pass


text.bind("<KeyRelease>", search)
root.mainloop()

Bu kodlarda şöyle bir kısım var:

index = text.index("insert")
    try:
        for i in tokenize.tokenize(io.BytesIO(text.get("{}.0".format(index[0]), "end").encode("utf-8")).readline)

Burada yapmak istediğim şey şuydu: kullanıcı alt satıra geçtiği zaman, text.get() fonksiyonunun satır parametresi de değişsin, böylece “1.0” ile “end” arasında değil de “2.0” ile “end” arasında veya “3.0” ile “end” arasında bir arama gerçekleştirilsin.

Yalnız aşağıdaki gibi bir sonuç aldım. Bu arada event kısmında "<KeyRelease>" var, yine tuş basımında etkili olan bir işlem bu. "<<Modified>>" yaptığımda ise hiç renklenme olmuyor.

2018-06-14%2017-12-50%20ekran%20g%C3%B6r%C3%BCnt%C3%BCs%C3%BC

Şurada birisi başarmış hızlandırmayı.

1 Beğeni