Cum se creează un plugin sublim 2

Sublime Text 2 este un editor de text extrem de personalizabil, care captează din ce în ce mai mult atenția programatorilor care caută un instrument puternic, rapid și modern. Astăzi, vom recrea pluginul nostru popular Sublime care trimite CSS prin API-ul Nettuts + Prefixr pentru o interfață ușoară CSS cross-browser.

Când ați terminat, veți avea o înțelegere solidă a modului în care este scris pluginul Prelimr Sublim și veți fi pregătit să începeți să scrieți propriile plugin-uri pentru editor!


Prefață: Terminologie și material de referință

Modelul de extensie pentru textul Sublime 2 este destul de plin de funcții.

Modelul de extensie pentru textul Sublime 2 este destul de plin de funcții. Există modalități de a modifica evidențierea sintaxei, cromul actual al editorului și toate meniurile. În plus, este posibil să creați noi sisteme de construire, completări automate, definiții de limbă, fragmente, macrocomenzi, legături de chei, legături de mouse și pluginuri. Toate aceste tipuri diferite de modificări sunt implementate prin fișiere care sunt organizate în pachete.

Un pachet este un dosar stocat în telefonul dvs. pachete director. Puteți accesa directorul Pachete făcând clic pe Preferințe> Răsfoiți pachetele ... meniu. Este, de asemenea, posibil să îmbinați un pachet într-un singur fișier, creând un fișier zip și schimbând extensia .sublimă-pachet. În acest tutorial vom discuta despre ambalaj.

Sublime vine împreună cu numeroase pachete diferite. Cele mai multe dintre pachetele incluse sunt specifice limbii. Acestea conțin definiții de limbă, sisteme de completare automată și sisteme de construire. În plus față de pachetele de limbă, există și alte două pachete: Mod implicit și Utilizator.
Mod implicit pachetul conține toate legările cheie standard, definițiile de meniu, setările fișierelor și o grămadă de pluginuri scrise în Python. Utilizator pachetul este special în faptul că este întotdeauna încărcat ultima. Acest lucru permite utilizatorilor să suprascrie setările implicite prin personalizarea fișierelor din acestea Utilizator pachet.

În timpul procesului de scriere a unui plugin, referința API Sublime Text 2 va fi esențială.

În timpul procesului de scriere a unui plugin, referința API Sublime Text 2 va fi esențială. In plus Mod implicit pachetul acționează ca o referință bună pentru a afla cum să faceți lucrurile și ce este posibil. O mare parte din funcționalitatea editorului este expusă prin intermediul comenzi. Orice operație, alta decât scrierea caracterelor, se realizează prin comenzi. Vizualizând Preferințe> Legături cheie - Implicitintroducerea în meniu, este posibil să găsiți o comoară de funcționalitate încorporată.

Acum că distincția dintre un plugin și un pachet este clară, să începem să scriem plugin-ul nostru.


Pasul 1 - Pornirea unui plugin

Sublime vine cu funcționalitate care generează un schelet de cod Python necesar pentru a scrie un plugin simplu. Selectează Instrumente> Plugin nou ... intrarea în meniu și un tampon nou va fi deschis cu această boilerplate.

import sublime, sublime_plugin class ExempluCommand (sublime_plugin.TextCommand): def run (self, edit): self.view.insert (edita, 0, "Hello, World!")

Aici puteți vedea că cele două module Sublime Python sunt importate pentru a permite utilizarea API-ului și este creată o nouă clasă de comandă. Înainte de a edita acest lucru și de a începe să creăm propriul plugin, să salvăm fișierul și să declanșăm funcționalitatea încorporată.

Când salvăm fișierul, vom crea un nou pachet pentru stocarea acestuia. Apăsați ctrl + s (Windows / Linux) sau cmd + s (OS X) pentru a salva fișierul. Dialogul de salvare se va deschide la Utilizator pachet. Nu salvați fișierul acolo, ci răsfoiți un dosar și creați un nou dosar numit Prefixr.

Pachete / ... - OCaml / - Perl / - PHP / - Prefixr / - Python / - R / - Rails / ... 

Acum salvați fișierul din dosarul Prefixr ca Prefixr.py. Nu contează exact ceea ce este numele fișierului, doar că se termină .py. Cu toate acestea, convențional vom folosi numele pluginului pentru numele fișierului.

Acum că plugin-ul este salvat, să-l încercăm. Deschideți consola Sublime apăsând ctrl +“. Aceasta este o consolă Python care are acces laAPI. Introduceți următorul Python pentru a testa noul plugin:

view.run_command ( 'exemplu')

Ar trebui sa vezi Salut Lume introdus în începutul fișierului plugin. Asigurați-vă că anulați această modificare înainte de a continua.


Pasul 2 - Tipuri de comenzi și denumire

Pentru pluginuri, Sublime oferă trei tipuri diferite de comenzi.

  • Comenzi text asigură accesul la conținutul fișierului / tamponului selectat prin intermediul unui Vedere obiect
  • Comenzi pentru ferestre să furnizeze referințe la fereastra curentă prin intermediul Fereastră obiect
  • Comenzi pentru aplicații nu au o referință la nicio fereastră sau fișier / tampon și sunt mai rar folosite

Deoarece vom manipula conținutul unui fișier / tampon CSS cu acest plugin, vom folosi sublime_plugin.TextCommand clasa ca baza a comenzii noastre personalizate Prefixr. Acest lucru ne aduce la tema numirii clasei de comandă.

În scheletul de plugin furnizat de Sublime, veți observa clasa:

clasă ExempluCommand (sublime_plugin.TextCommand):

Când am vrut să executăm comanda, am executat următorul cod în consola:

view.run_command ( 'exemplu')

Sublime va lua orice clasă care extinde unul din sublime_plugin clase
(TextCommand, WindowCommand sau ApplicationCommand), eliminați sufixul Comanda și apoi convertiți CamelCaseîn underscore_notation pentru numele comenzii.

Astfel, pentru a crea o comandă cu numele prefixr, clasa trebuie să fie PrefixrCommand.

clasa PrefixrCommand (sublime_plugin.TextCommand):

Pasul 3 - Selectarea textului

Una dintre caracteristicile cele mai utile ale Sublime este abilitatea de a avea mai multe selecții.

Acum, când pluginul nostru a fost denumit corect, putem începe procesul de preluare a CSS din buffer-ul curent și trimiterea acestuia la API-ul Prefixr. Una dintre caracteristicile cele mai utile ale Sublime este abilitatea de a avea mai multe selecții. Pe măsură ce apucăm textul selectat, trebuie să ne scriem mufa în mâna nu numai a primei selecții, ci și a tuturor.

Deoarece scriem o comandă de text, avem acces la vizualizarea curentă prin self.view. sel () metodă a Vedere obiectul returnează un proces iterabil regionset din selecțiile curente. Începem prin scanarea prin acestea pentru bretele curl. În cazul în care nu sunt prezenți coastele curbate, putem extinde selecția la brațele înconjurătoare, pentru a ne asigura că întregul bloc este prefixat. Indiferent dacă selecția noastră este inclusă sau nu, va fi utilă și ulterior să știm dacă putem modifica spațiile albe și formatarea rezultatului obținut de la API-ul Prefixr.

braces = False sels = auto.view.sel () pentru sel în sels: dacă self.view.substr (sel) .find ('')! = -1: braces =

Acest cod înlocuiește conținutul scheletului alerga() metodă.

Daca nu am gasit nici o bretele netede, bifam fiecare selectie si ajustam selectiile la cea mai apropiata bratara de inchidere. Apoi, folosim comanda încorporată expand_selection cu la arg setat la paranteze pentru a ne asigura că conținutul complet al fiecărui bloc CSS este selectat.

dacă nu brațele: new_sels = [] pentru sel în sels: new_sels.append (self.view.find ('\', sel.end ())) sels.clear () pentru sel în new_sels: sels.add ) self.view.run_command ("expand_selection", "to": "paranteze")

Dacă doriți să vă verificați încă o dată activitatea, comparați sursa cu fișierul Prefixr-1.py în fișierul sursă cod zip.


Pasul 4 - Filetarea

Pentru a preveni întreruperea unei alte conexiuni, trebuie să vă asigurați că apelurile API Prefixr se întâmplă în fundal.

În acest moment, selecțiile au fost extinse pentru a apuca conținutul complet al fiecărui bloc CSS. Acum, trebuie să le trimitem la API-ul Prefixr. Aceasta este o cerere simplă HTTP, pe care o vom folosi urllib și urllib2 module pentru. Cu toate acestea, înainte de a începe lansarea cererilor de pe web, trebuie să ne gândim la modul în care o solicitare de web potențial lagos ar putea afecta performanța editorului. Dacă, dintr-un anumit motiv, utilizatorul se află într-o conexiune cu o latență ridicată sau lentă, solicitările către API-ul Prefixr pot dura cu ușurință câteva secunde sau mai mult.

Pentru a împiedica o conexiune slabă să întrerupă alte lucrări, trebuie să ne asigurăm că apelurile API Prefixr se întâmplă în fundal. Dacă nu știți nimic despre filetare, o explicație foarte importantă este că firele sunt o modalitate pentru un program de a programa mai multe seturi de coduri pentru a rula aparent în același timp. Este esențial în cazul nostru deoarece permite codului care trimite date și așteaptă un răspuns de la API-ul Prefixr să împiedice înghețarea restului interfeței utilizator sublime.


Pasul 5 - Crearea de fire

Vom folosi Python-ul filetat modul de creare a firelor. Pentru a utiliza modulul de filetare, vom crea o clasă nouă care se extinde threading.Thread denumit PrefixrApiCall. Clasele care se extind threading.Thread include a alerga() metoda care conține tot codul care urmează să fie executat în firul.

clasa PrefixrApiCall (threading.Thread): def __init __ (auto, sel, string, timeout): self.sel = sel self.original = șirul self.timeout = timeout self.result = (request) = request urllib2.Request ('http://prefixr.com/api/index.php', date, headers = "urllib.urlencode" (Urllib2.HTTPError) ca (e): err = '% =' s: eroare HTTP% s contactând API '% (__name__, str (e.code)) cu excepția (urllib2.URLError) ca (e): err ='% s: e.reason)) sublime.error_message (err) self.result = False

Aici folosim firul __ metodei __init () pentru a seta toate valorile care vor fi necesare în timpul solicitării web. alerga() metoda conține codul de deblocare și executarea cererii HTTP pentru API-ul Prefixr. Deoarece firele funcționează concomitent cu alte coduri, nu este posibilă returnarea directă a valorilor. În schimb, am stabilit self.result la rezultatul apelului.

Deoarece am început să folosim mai multe module în pluginul nostru, trebuie să le adăugăm la declarațiile de import din partea de sus a scriptului.

import urllib import urllib2 importare filetare

Acum, că avem o clasă threaded pentru a efectua apelurile HTTP, trebuie să creați un fir pentru fiecare selecție. Pentru a face acest lucru, vom reveni în alerga() metoda noastră PrefixrCommand și folosiți următoarea buclă:

thread = [] pentru sel în sels: string = auto.view.substr (sel) thread = PrefixrApiCall (sel, string, 5) threads.append (thread) thread.start

Urmărim fiecare fir pe care îl creăm și apoi sunăm start() metoda de a începe fiecare.

Dacă doriți să vă verificați încă o dată activitatea, comparați sursa cu fișierul Prefixr-2.py în fișierul sursă cod zip.


Pasul 6 - Pregătirea pentru rezultate

Acum, că am început cererile actuale de Prefixr API, trebuie să fixăm câteva detalii ulterioare înainte de tratarea răspunsurilor.

În primul rând, ștergem toate selecțiile, deoarece le-am modificat mai devreme. Mai târziu le vom readuce la o stare rezonabilă.

self.view.sel (). clar ()

În plus, vom începe un nou Editați | × obiect. Aceasta grupează operațiile pentru anularea și refacerea. Specificăm că creăm un grup pentru prefixr comanda.

editează = self.view.begin_edit ('prefixr')

Ca pas final, numim o metodă pe care o vom scrie în continuare, care va rezolva rezultatul cererilor API.

self.handle_threads (editare, fire, bretele)

Pasul 7 - Manipularea firelor

În acest moment firele noastre rulează, sau chiar finalizate. Apoi, trebuie să implementăm handle_threads () metoda pe care tocmai am referit-o. Această metodă va trece printr-o listă de fire și va căuta fire care nu mai rulează.

def handle_threads (auto, editează, fire, brațe, offset = 0, i = 0, dir = 1): next_threads = [] pentru fire în thread: if thread.is_alive (): next_threads.append rezultatul == False: continua offset = auto.replace (edita, thread, braces, offset) threads = next_threads

Dacă un fir este încă în viață, îl adăugăm la lista de fire pentru a verifica din nou mai târziu. Dacă rezultatul a fost un eșec, noi îl ignorăm, însă pentru rezultate bune noi numim noi a inlocui() metodă pe care o vom scrie în curând.

Dacă există subiecte care sunt încă în viață, trebuie să le verificăm din nou în scurt timp. În plus, este o îmbunătățire excelentă a interfeței cu utilizatorul pentru a furniza un indicator de activitate care să arate că plugin-ul nostru rulează încă.

dacă linia (firele): # Aceasta animă un mic indicator de activitate în zona de stare înainte de = i% 8 după = (7) - înainte dacă nu după: dir = -1 dacă nu înainte: dir = 1 i + = dir self. view.set_status ('prefixr', 'Prefixr [% s =% s]'% \ ("* înainte", după *) sublime.set_timeout (lambda: self.handle_threads dir), 100) retur

Prima secțiune a codului utilizează o valoare intregă simplă stocată în variabila eu pentru a muta un = înainte și înapoi între două paranteze. Ultima parte este însă cea mai importantă. Acest lucru îi spune lui Sublime să ruleze handle_threads ()din nou, cu valori noi, în alte 100 de milisecunde. Acesta este la fel ca setTimeout () funcția în JavaScript.

lambda cuvântul cheie este o caracteristică a Python care ne permite să creăm o nouă funcție anonimă sau anonimă.

sublime.set_timeout () metoda necesită o funcție sau o metodă și numărul de milisecunde până la executarea acesteia. Fără lambda am putea spune că vrem să fugim handle_threads (), dar nu am putea să specificăm parametrii.

Dacă toate firele au fost finalizate, nu este necesar să setăm un alt interval de timp, dar în cele din urmă terminăm grupul de anulați și actualizăm interfața de utilizator pentru a permite utilizatorului să știe că totul se face.

auto.view.end_edit (edita) self.view.erase_status ('prefixr') selections = len (self.view.sel ()) sublime.status_message (' dacă selecțiile == 1 altceva '))

Dacă doriți să vă verificați încă o dată activitatea, comparați sursa cu fișierul Prefixr-3.py în fișierul sursă cod zip.


Pasul 8 - Realizarea înlocuirilor

Cu firele noastre manipulate, acum trebuie doar să scriem codul care înlocuiește CSS original cu rezultatul din API-ul Prefixr. După cum am menționat mai devreme, vom scrie o metodă numită a inlocui().

Această metodă acceptă un număr de parametri, inclusiv Editați | × obiect pentru anulare, firul care a capturat rezultatul din API-ul Prefixr, în cazul în care selecția inițială a inclus bretele și, în final, selecția offset.

def inlocuire (sine, editare, thread, bretele, offset): sel = thread.sel original = thread.original result = thread.result # Aici ajustam fiecare selectie pentru orice text deja introdus daca este offset: sel = sublime. (sel.begin () + offset, sel.end () + offset)

Decalajul este necesar atunci când se ocupă de mai multe selecții. Atunci când înlocuim un bloc de CSS cu CSS prefixat, lungimea acestui bloc va crește. Offsetul asigură că înlocuim conținutul corect pentru selecțiile ulterioare, deoarece textul poziționează toate schimbările la fiecare înlocuire.

Următorul pas este de a pregăti rezultatul din API-ul Prefixr pentru a fi înlocuit ca CSS de înlocuire. Aceasta include transformarea capăturilor de linie și a indentării pentru a se potrivi cu documentul curent și selecția originală.

rezultatul = self.normalize_line_endings (rezultat) (prefix, principal, sufix) = self.fix_whitespace (original, rezultat, sel,

Ca ultim pas, am setat selecția utilizatorului pentru a include sfârșitul ultimei linii a noului CSS pe care l-am inserat și apoi returnați offsetul ajustat pentru a fi utilizat pentru orice alte selecții.

end_point = sel.begin () + len (prefix) + len (principală) self.view.sel () adăuga (sublime.Region (end_point, end_point) original)

Dacă doriți să vă verificați încă o dată activitatea, comparați sursa cu fișierul Prefixr-4.py în fișierul sursă cod zip.


Pasul 9 - Manipularea spațiilor libere

Am folosit două metode personalizate în timpul procesului de înlocuire pentru a pregăti noul CSS pentru document. Aceste metode iau rezultatul Prefixr și îl modificați pentru a se potrivi cu documentul curent.

normalize_line_endings () ia șirul și se asigură că acesta se potrivește cu sfârșitul liniei fișierului curent. Noi folosim Setări clasa de la Sublime API pentru a obține terminalele liniei corespunzătoare.

Definiți normalize_line_endings (auto, șir): string = string.replace ('\ r \ n', '\ n'). ('\ n', '\ r \ n') dacă linia_endings == 'windows': string = string.replace (' '\ r') șir de returnare

fix_whitespace () metoda este ceva mai complicată, dar face același fel de manipulare, doar pentru indentare și spațiu liber în blocul CSS. Această manipulare funcționează într-adevăr într-adevăr cu un singur bloc de CSS, deci ieșim dacă unul sau mai multe bretele au fost incluse în selecția inițială.

def fix_whitespace (self, original, prefixed, sel, braces): # Dacă sunt prezenți brațele, putem face tot magia spațiilor libere în cazul bretelelor: return ("prefixed")

În caz contrar, începem prin determinarea nivelului indentării CSS inițial. Aceasta se face prin căutarea spațiilor libere la începutul selecției.

(rând, col) = auto.view.rowcol (sel.begin ()) indent_region = auto.view.find ('^ \ s +', auto.view.text_point (rând, 0) indent_region.begin ()) [0] == rând: indent = auto.view.substr (indent_region) alt: indent = "

Apoi, decolim spațiul alb din CSS prefixat și folosim setările de vizualizare curente pentru a alinia CSS-ul tăiat la nivelul original folosind fie filele, fie spațiile, în funcție de setările curente ale editorului.

prefixed = prefixed.strip () prefixed = re.sub (remix) ('^ \ s +', re.M) ) tab_size = int (setări.get ('tab_size', 8)) indent_characters = '\ t' dacă use_spaces: indent_characters = "* tab_size prefixed = prefixed.replace ('

Finalizăm metoda folosind spațiul alb inițial și de sfârșit, pentru a ne asigura că noul CSS prefixat se potrivește exact în locul originalului.

match = re.search ('(\ s *) \ Z', original) suffix = match.groups () [0] retur (prefix, prefix, sufix)

Cu fix_whitespace () metodă am folosit modulul (re) modul de expresie regulat Python, așa că trebuie să îl adăugăm în lista importurilor din partea de sus a scriptului.

import re

Și cu aceasta, am terminat procesul de scriere prefixr Următorul pas este de a face comanda ușor de rulare prin furnizarea unei comenzi rapide de la tastatură și a unei intrări în meniu.


Pasul 10 - Legarea cheilor

Cele mai multe setări și modificări care pot fi făcute în Sublime se fac prin fișiere JSON, și acest lucru este valabil pentru legăturile cheie. În mod obișnuit, legăturile cheie sunt specifice OS-ului, ceea ce înseamnă că trei fișiere de legare cheie vor trebui create pentru plugin-ul dvs. Fișierele ar trebui să fie numite Implicit (Windows) .sublime-keymap, Implicit (Linux) .sublime-keymap și Implicit (OSX) .sublime-keymap.

 Prefixr / ... - Implicit (Linux) .sublime-keymap - Implicit (OSX) .sublime-keymap - Implicit (Windows) .sublime-keymap - Prefixr.py

.sublimă-keymap fișierele conțin o matrice JSON care conține obiecte JSON pentru a specifica legăturile cheie. Obiectele JSON trebuie să conțină a chei și comanda cheie și poate conține, de asemenea, a args dacă comanda necesită argumente. Cea mai grea parte din alegerea legării cheilor este să vă asigurați că legarea cheilor nu este deja utilizată. Acest lucru se poate face prin accesarea Preferințe> Legături cheie - Implicit introduceți meniul și căutați legătura de chei pe care doriți să o utilizați. Odată ce ați găsit o obligație nefolosită corespunzător, adăugați-o la dvs. .sublimă-keymap fișiere.

 ["tastele": ["ctrl + alt + x"], "comanda": "prefixr"]

În mod normal, legăturile de chei Linux și Windows sunt aceleași. Cheia cmd pe OS X este specificată de șir super în .sublimă-keymap fișiere. La portarea unei chei obligatorii pe OS, este comună pentru ctrl cheie pe Windows și Linux pentru a fi schimbat pentru super pe sistemul de operare X. Cu toate acestea, aceasta nu poate fi întotdeauna cea mai naturală mișcare a mâinilor, așa că, dacă este posibil, încercați și încercați legăturile de taste pe o tastatură reală.


Pasul 11 ​​- intrări în meniu

Unul dintre lucrurile mai subtile despre extinderea Sublime este că este posibil să adăugați elemente în structura meniului prin crearea .sublimă-meniu fișiere. Menufiles trebuie să fie denumite nume specifice pentru a indica ce meniu le afectează:

  • Main.sublime-meniu controlează meniul principal al programului
  • Side Bar.sublime-meniu controlează meniul cu clic dreapta pe un fișier sau un dosar din bara laterală
  • Context.sublime-meniu controlează meniul cu clic dreapta pe un fișier editat

Există o întreagă mână de alte fișiere de meniu care afectează diverse alte meniuri în întreaga interfață. Navigarea prin Mod implicit pachetul este cel mai simplu mod de a învăța despre toate acestea.

Pentru Prefixr dorim să adăugăm un element de meniu la Editați | × meniu și câteva intrări la Preferințe meniu pentru setări. Următorul exemplu este structura JSON pentru Editați | × meniu. Am omis intrările pentru Preferințe meniu, deoarece acestea sunt destul de verbose fiind imbricate la câteva niveluri profunde.

"id": "edita", "copii": "id": "wrap", "comanda"

Singura piesă de care trebuie să acordați atenție este id chei. Prin specificarea ida unei intrări de meniu existente, este posibilă adăugarea unei intrări fără redefinirea structurii existente. Dacă deschideți Main.sublime-meniu fișier de la Mod implicit pachet și răsfoiți în jurul, puteți stabili ce idpe care doriți să vă adăugați intrarea.

În acest moment, pachetul dvs. Prefixr ar trebui să pară aproape identic cu versiunea oficială de pe GitHub.


Pasul 12 - Distribuirea pachetului dvs.

Acum, că ați făcut timp pentru a scrie un plugin Sublime util, este timpul să intrați în mâinile altor utilizatori.

Sublime suportă distribuirea unui fișier zip al unui director pachet ca o modalitate simplă de partajare a pachetelor. Pur și simplu zip folderul pachetului și modificați extensia .sublimă-pachet. Alți utilizatori pot plasa acum acest lucru în lor Pachete instalate director și reporniți Sublime pentru a instala pachetul.

Odată cu disponibilitatea facilă pentru o mulțime de utilizatori, având pachetul disponibil prin intermediul pachetului de control asigură utilizatorilor upgrade-ul automat la cele mai recente actualizări.

În timp ce acest lucru poate funcționa cu siguranță, există, de asemenea, un manager de pachete pentruSublime numit Controlul pachetului care acceptă o listă de pachete și actualizări automate. Pentru a vă adăuga pachetul la canalul implicit, pur și simplu găzduiți-l pe GitHubor BitBucket și apoi introduceți fișierul de canal (pe GitHub sau BitBucket), adăugați depozitul și trimiteți o cerere de tragere. Odată ce cererea de tragere este acceptată, pachetul dvs. va fi disponibil pentru mii de utilizatori folosind Sublime. Odată cu disponibilitatea facilă pentru o mulțime de utilizatori, având pachetul disponibil prin intermediul pachetului de control asigură utilizatorilor upgrade-ul automat la cele mai recente actualizări.

Dacă nu doriți să găzduiți pe GitHub sau BitBucket, există un canal / depozit customJSON care poate fi utilizat pentru a găzdui oriunde, oferind în același timp pachetul tuturor utilizatorilor. De asemenea, oferă funcționalități avansate, cum ar fi specificarea disponibilității pachetelor prin sistemul de operare. Consultați pagina PackageControl pentru mai multe detalii.


Scrieți câteva pluginuri!

Acum că am acoperit pașii pentru a scrie un plugin Sublime, este timpul să vă scufundați! Comunitatea plugin Sublime creează și publică funcții noi aproape în fiecare zi. Cu fiecare lansare, Sublime devine din ce în ce mai puternic și versatil. Forumul Sublime Text este un loc minunat pentru a obține ajutor și a discuta cu ceilalți despre ceea ce construiți.

Cod