Ukázka, jak docílit barevné syntaxe v udělátku Text pomocí tagů.
Naprogramovat barevnou syntaxi v Tkinteru není nic tak složitého. Použijeme několik nových metod souvisejících s tagy a naučíme se používat Tkinterovské výrazy. Jako vzor budeme mít IDLE, ale budeme obarvovat pouze řetězce, klíčová slova a názvy vybraných funkcí.
Další způsoby indexace
V první lekci o textovém editoru jsme si ukazovali některé způsoby indexace. Dnes si tento seznam rozšíříme o několik dalších:
- CURRENT – Jedná se o index, který je nejbližší aktuální poloze myši.
- "@x,y" – Zjistí, jaký textový index je na souřadnicích x,y.
Výrazy
Pomocí výrazů můžete upravovat jakýkoliv textový index. Tyto výrazy jsou:
- "+2 chars" – přičte k indexu 2 znaky. Takže z 1.0 se stane 1.2
- "-2 chars" – odečte z indexu 2 znaky. Takže z 1.2 se stane 1.0
- "+ 2 lines" a "- 2 lines" – posouvá pozici po celých řádcích dopředu nebo dozadu. Je-li to možné, pozice sloupce se zachovává, je-li řádek příliš krátký, je pozice na konci toho řádku.
- "wordstart" a "wordend" – posune pozici na začátek nebo konec slova. Slova jsou posloupnosti znaků, číslic a podtržítka nebo jediného nemezerového znaku.
Je možné používat pro chars zkratku c a pro lines zkratku l. Takže pár příkladů:
"%s wordstart" % CURRENT#Vrátí začátek slova, na kterém se právě nachází myš
"insert -5c" #značí 5 znaků před aktuální pozicí
A tady už je praktická ukázka:
# -*- coding: cp1250 -*-
#Po kliknutím myši vypíše text, na kterém se právě nachází kurzor myši.
from Tkinter import*
okno=Tk()
text=Text()
text.pack()
def akce(akce):
print text.get(text.index("%s wordstart" % CURRENT),text.index("%s wordend"%CURRENT))
text.bind('<1>',akce)
mainloop()
Kostra třídy
Princip celého programu bude v tom, že se bude obarvovat pouze ta řádka, na které právě píšeme, protože barvení celého obsahu udělátka při každém stisknutí klávesy by bylo časově náročné. Takže pokud vložíte do Textu nějaký text, neobarví se. Tento nedostatek se pokusíme opravit v další lekci.
# -*- coding: cp1250 -*-
from Tkinter import*
import __builtin__,keyword#Z těchto modulů budeme načítat slova k obarvení.
class BarevnaSyntax:
def __init__(self,text,slovnik):
self.slovnik=slovnik
self.text=text
self.text.bind_class("Text","<Key>",self.klavesa)
def klavesa(self,evt):
pass
def obarvi_radek(self,radek):
pass
def obarvi_str(self,co,radek):
pass
def test():
slovnik={}
for prvek in keyword.kwlist:
slovnik[prvek]="klicove_slovo"
for prvek in dir(__builtin__):
slovnik[prvek]="funkce"
text=Text(font="Courier 10")
text.pack()
text.tag_config("funkce",foreground="#900090")
text.tag_config("klicove_slovo",foreground="#ff8000")
text.tag_config("str",foreground="#00aa00")
BarevnaSyntax(text,slovnik)
mainloop()
if __name__ == "__main__":
test()
Ve funkci __init__ jsme použili metodu bind_class(udělátko, akce, funkce). Rozdíl mezi bind_class a bind je ten, že v případě bind("
def klavesa(self,akce):
#Smaž vybraný text.
self.text.insert(INSERT,akce.char)#v akce.char je uloženo písmeno, které jsme zmáčkli.
#Tady později zavoláme metodu na obarvení řádku.
Ještě bychom měli ošetřit událost, co se stane, pokud je nějaký text vybraný a uživatel zmáčkne klávesu. V takové situaci by se měl vybraný text před vložením stisknutého znaku smazat.
Obarvení řetězce
Řetězec bude moci být uvozen klasickými uvozovkami (") a apostrofem ('). Ze všeho nejdříve musíme zjistit, jaké části textu přiřadíme tag. Na to použijeme metodu udělátka Text text.search(co,start, stopindex). Program se vždy bude snažit najít dvě párové uvozovky. Tím pádem můžou nastat 3 případy:
- Program nalezne dvě párové uvozovky – obarví prostor mezi nimi.
- Program nalezne pouze jednu uvozovku – obarví prostor od uvozovky až do konce řádku.
- Program nenalezl žádnou uvozovku – program vyskočí z cyklu.
def obarvi_str(self,co,radek):
start='%s.0' % radek#řádek, který obarvuji
while 1:
seznam=[]
for x in range(2):
pos=self.text.search(co,start, stopindex='%s.end'%self.text.index(radek).split('.')[0])
if not pos:#Program nic nenalezl...
break
seznam.append(pos)
#Seznam nyní obsahuje maximálně 2 indexy. Nyní by měla následovat kontrola, jakou jsem načrtl před chvílí.
Obarvení funkcí a klíčových slov
Programu budeme data předávat ve tvaru slovníku, který by mohl vypadat takto:
slovnik={"None":"funkce","if":"klicove_slovo", "del":"klicove_slovo"}
#"funkce" a "klicove_slovo" budou předdefinované tagy
Nyní musíme vymyslet, jak poznat, kterou část obarvit. Nejlepší řešení je asi použít wordstart a wordend. Tedy, procházet všechna slova na řádce a pokud je nějaké ve slovníku slov k obarvení, obarvit ho.
def obarvi_radek(self,radek):
radka=self.text.get('%s.0'%radek,'%s.end' % radek)#aktuální řádek
start='%s.1'%radek#začni hledat na prvním slově
while 1:
#zacatek=začátek aktuálního slova
#konec=konec aktuálního slova
slovo=self.text.get(zacatek,konec)#aktuální slovo
#Kontrola, jestli nalezené slovo není v self.slovnik.
#Zvětšení hodnoty start.
if self.text.compare(self.text.index(start),">",self.text.index('%s.end'%radek)):
break
self.obarvi_str("'",radek)
self.obarvi_str('"',radek)
Než začneme obarvovat, musíme odstranit všechny tagy z aktuálního řádku, aby bylo obarvení korektní. O to by se měly postarat metody tag_names a tag_remove. Jelikož metoda tag_names přijímá jako argument pouze jeden index, musíme s ní prozkoumat celý řádek znak po znaku:
for x in range(len(radka)):
jmena=self.text.tag_names('%s.%s' % (radek,x))
Nyní je na vás, abyste nalezené tagy odstranili. Problém obarvení slova se dá vyřešit pomocí metod has_key a tag_add. Teď stačí zvýšit hodnotu start a metoda je hotová. Nejjednodušší řešení by bylo prostě:
start=start+"+1c"
Ale toto by bylo zbytečně časově náročné, protože každé slovo by se obarvovalo víckrát. Proto navrhuji k hodnotě start přičíst délku nalezeného slova.
Ještě jedna metoda vám je neznámá, a to metoda compare. S její pomocí můžeme zjistit vztah mezi dvěma indexy (větší, menší, rovno, …). V našem případě kontroluje, jestli je vyhledávaný index pořád na stejné řádce. Pokud není, program vyskočí z cyklu. Nyní musíme dopsat volání metody self.obarvi_radek z metody self.klavesa:
#Přidejte tento kód na konec metody self.klavesa.
self.obarvi_radek(self.text.index(INSERT).split('.')[0])#Jako argument předej číslo řádku
Tím je naše třída hotova.
Náš program má stále určité nedostatky, v dalším díle se je pokusíme opravit.