├── .gitignore ├── AboutDlg.cpp ├── AboutDlg.h ├── FuncItemManager.cpp ├── FuncItemManager.h ├── ZenCoding-Python.cpp ├── ZenCoding-Python.rc ├── ZenCoding-Python.sln ├── ZenCoding-Python.vcxproj ├── ZenCoding-Python.vcxproj.filters ├── ZenCodingPython ├── npp_zen_coding.py └── zencoding │ ├── __init__.py │ ├── actions │ ├── __init__.py │ ├── basic.py │ ├── token.py │ └── traverse.py │ ├── filters │ ├── __init__.py │ ├── comment.py │ ├── css.py │ ├── escape.py │ ├── format-css.py │ ├── format.py │ ├── haml.py │ ├── html.py │ ├── single-line.py │ ├── trim.py │ ├── xsd.py │ └── xsl.py │ ├── html_matcher.py │ ├── interface │ ├── __init__.py │ ├── editor.py │ └── file.py │ ├── my_zen_settings.py │ ├── parser │ ├── __init__.py │ ├── abbreviation.py │ ├── css.py │ ├── utils.py │ └── xml.py │ ├── resources.py │ ├── utils.py │ ├── zen_editor.py │ └── zen_settings.py ├── dllmain.cpp ├── resource.h ├── stdafx.cpp ├── stdafx.h └── targetver.h /.gitignore: -------------------------------------------------------------------------------- 1 | Debug/ 2 | Release/ 3 | ipch/ 4 | *.suo 5 | *.user 6 | *.sdf 7 | *.opensdf 8 | *.aps 9 | -------------------------------------------------------------------------------- /AboutDlg.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "stdafx.h" 3 | #include "AboutDlg.h" 4 | #include "resource.h" 5 | 6 | AboutDialog::AboutDialog(void) 7 | { 8 | } 9 | 10 | AboutDialog::~AboutDialog(void) 11 | { 12 | } 13 | 14 | void AboutDialog::doDialog() 15 | { 16 | if (!isCreated()) 17 | create(IDD_ABOUTDLG); 18 | 19 | goToCenter(); 20 | } 21 | 22 | 23 | BOOL CALLBACK AboutDialog::run_dlgProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM /* lParam */) 24 | { 25 | switch (Message) 26 | { 27 | case WM_INITDIALOG : 28 | { 29 | return TRUE; 30 | } 31 | 32 | 33 | case WM_COMMAND : 34 | { 35 | switch (wParam) 36 | { 37 | case IDOK : 38 | case IDCANCEL : 39 | display(FALSE); 40 | return TRUE; 41 | 42 | default : 43 | break; 44 | } 45 | 46 | } 47 | } 48 | return FALSE; 49 | } -------------------------------------------------------------------------------- /AboutDlg.h: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | 4 | class AboutDialog : public StaticDialog 5 | { 6 | public: 7 | AboutDialog(void); 8 | ~AboutDialog(void); 9 | 10 | void init(HINSTANCE hInst, NppData nppData) 11 | { 12 | _nppData = nppData; 13 | Window::init(hInst, nppData._nppHandle); 14 | }; 15 | 16 | void doDialog(); 17 | BOOL CALLBACK AboutDialog::run_dlgProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam); 18 | 19 | private: 20 | NppData _nppData; 21 | }; 22 | -------------------------------------------------------------------------------- /FuncItemManager.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "PluginInterface.h" 3 | 4 | #include "FuncItemManager.h" 5 | 6 | FuncItemManager::FuncItemManager() 7 | : m_funcItems(NULL) 8 | { 9 | } 10 | 11 | 12 | FuncItemManager::~FuncItemManager() 13 | { 14 | for(std::list::iterator it = m_funcList.begin(); it != m_funcList.end(); ++it) 15 | { 16 | if ((*it)->_pShKey != NULL) 17 | { 18 | delete (*it)->_pShKey; 19 | } 20 | delete (*it); 21 | } 22 | 23 | if (m_funcItems != NULL) 24 | { 25 | delete [] m_funcItems; 26 | } 27 | } 28 | 29 | 30 | 31 | 32 | int FuncItemManager::addFunction(const TCHAR *functionName, 33 | PFUNCPLUGINCMD function, 34 | UCHAR key, 35 | int modifiers, 36 | bool initToChecked /* = false */) 37 | { 38 | ShortcutKey *shkey = new ShortcutKey(); 39 | shkey->_key = key; 40 | shkey->_isCtrl = (modifiers & MODIFIER_CTRL) == MODIFIER_CTRL; 41 | shkey->_isAlt = (modifiers & MODIFIER_ALT) == MODIFIER_ALT; 42 | shkey->_isShift = (modifiers & MODIFIER_SHIFT) == MODIFIER_SHIFT; 43 | return addFunction(functionName, function, shkey, initToChecked); 44 | } 45 | 46 | int FuncItemManager::addFunction(const TCHAR *functionName, 47 | PFUNCPLUGINCMD function, 48 | ShortcutKey* shortcutKey /* = NULL */, 49 | bool initToChecked /* = false */) 50 | { 51 | FuncItem *item = new FuncItem(); 52 | _tcscpy_s<64>(item->_itemName, functionName); 53 | item->_pFunc = function; 54 | item->_init2Check = initToChecked; 55 | item->_pShKey = shortcutKey; 56 | 57 | m_funcList.push_back(item); 58 | return m_funcList.size() - 1; 59 | } 60 | 61 | 62 | void FuncItemManager::addSplitter() 63 | { 64 | FuncItem *item = new FuncItem(); 65 | item->_itemName[0] = _T('\0'); 66 | item->_pFunc = NULL; 67 | m_funcList.push_back(item); 68 | } 69 | 70 | 71 | FuncItem* FuncItemManager::getFuncItems(int *funcCount) 72 | { 73 | if (m_funcItems != NULL) 74 | { 75 | delete [] m_funcItems; 76 | } 77 | 78 | *funcCount = m_funcList.size(); 79 | m_funcItems = new FuncItem[(*funcCount)]; 80 | 81 | // Copy the list items into the m_funcList. 82 | // Sadly this has to make an actual copy of each funcItem, but that's life.. 83 | 84 | int pos = 0; 85 | for(std::list::iterator it = m_funcList.begin(); it != m_funcList.end(); ++it, ++pos) 86 | { 87 | m_funcItems[pos] = *(*it); 88 | } 89 | 90 | return m_funcItems; 91 | } 92 | 93 | -------------------------------------------------------------------------------- /FuncItemManager.h: -------------------------------------------------------------------------------- 1 | #ifndef FUNCITEMMANAGER_H 2 | #define FUNCITEMMANAGER_H 3 | 4 | 5 | enum KeyModifiers 6 | { 7 | MODIFIER_NONE = 0, 8 | MODIFIER_CTRL = 1, 9 | MODIFIER_ALT = 2, 10 | MODIFIER_SHIFT = 4 11 | }; 12 | 13 | class FuncItemManager 14 | { 15 | public: 16 | FuncItemManager(); 17 | ~FuncItemManager(); 18 | 19 | int addFunction(const TCHAR *functionName, 20 | PFUNCPLUGINCMD function, 21 | ShortcutKey* shortcutKey = NULL, 22 | bool initToChecked = false); 23 | 24 | int addFunction(const TCHAR *functionName, 25 | PFUNCPLUGINCMD function, 26 | UCHAR key, 27 | int modifiers, 28 | bool initToChecked = false); 29 | 30 | void addSplitter(); 31 | 32 | FuncItem* getFuncItems(int *funcCount); 33 | 34 | private: 35 | std::list m_funcList; 36 | FuncItem* m_funcItems; 37 | 38 | 39 | }; 40 | 41 | 42 | #endif // FUNCITEMMANAGER_H -------------------------------------------------------------------------------- /ZenCoding-Python.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ZenCoding Python Plugin 4 | This code needs tidying up - it should be split out into classes 5 | - there's waaaay too many global variables (i.e. more than none :) 6 | */ 7 | 8 | #include "stdafx.h" 9 | #include "AboutDlg.h" 10 | #include "PluginInterface.h" 11 | #include "FuncItemManager.h" 12 | 13 | #define CHECK_INITIALISED() if (!g_initialised){ initialise(); } 14 | 15 | struct SCNotification 16 | { 17 | NMHDR nmhdr; 18 | }; 19 | 20 | 21 | /* Info for Notepad++ */ 22 | CONST TCHAR PLUGIN_NAME[] = _T("Zen Coding - Python"); 23 | 24 | FuncItem *funcItem = NULL; 25 | 26 | /* Global data */ 27 | NppData nppData; 28 | HANDLE g_hModule = NULL; 29 | bool g_pythonFailure = false; 30 | bool g_initialised = false; 31 | bool g_expandIsTab = false; 32 | bool g_autoSelectProfile = true; 33 | bool g_watchSave = false; 34 | TCHAR g_settingsFile[MAX_PATH]; 35 | TCHAR g_iniPath[MAX_PATH]; 36 | 37 | int g_fiAutoProfile = -1; 38 | int g_fiProfileXhtml = -1; 39 | int g_fiProfileHtml = -1; 40 | int g_fiProfileXml = -1; 41 | int g_fiProfilePlain = -1; 42 | int g_currentProfileIndex = -1; 43 | tstring g_currentProfile; 44 | 45 | /* Dialogs */ 46 | AboutDialog aboutDlg; 47 | 48 | 49 | FuncItemManager *g_funcItemManager = NULL; 50 | 51 | 52 | // Runs a string in python 53 | void runScript(const TCHAR *script); 54 | void runString(const TCHAR *str, int messageType = PYSCR_EXECSTATEMENT); 55 | 56 | // Checks if the key is tab on it's own 57 | bool keyIsTab(const ShortcutKey& key); 58 | 59 | // Sets the profile (ticks menu item, and informs Python) 60 | void setProfile(const TCHAR *profileName, int cmdIndex); 61 | 62 | 63 | // Settings 64 | void loadSettings(); 65 | void saveSettings(); 66 | 67 | void initialise() 68 | { 69 | TCHAR configPath[MAX_PATH]; 70 | ::SendMessage(nppData._nppHandle, NPPM_GETPLUGINSCONFIGDIR, MAX_PATH, reinterpret_cast(configPath)); 71 | 72 | tstring my_zen_settings(configPath); 73 | my_zen_settings.append(_T("\\ZenCodingPython\\zencoding\\my_zen_settings.py")); 74 | 75 | 76 | if (!::PathFileExists(my_zen_settings.c_str())) 77 | { 78 | std::ofstream mySettingsFile(my_zen_settings.c_str()); 79 | 80 | mySettingsFile << "my_zen_settings = {\n" 81 | " 'html': {\n" 82 | " 'abbreviations': {\n" 83 | " 'jq': '',\n" 84 | " 'demo': '
'\n" 85 | " }\n" 86 | " }\n" 87 | "}\n"; 88 | mySettingsFile.close(); 89 | 90 | } 91 | 92 | tstring zen_module(_T("sys.path.append(r'")); 93 | zen_module.append(configPath); 94 | zen_module.append(_T("\\ZenCodingPython')\nimport npp_zen_coding\n")); 95 | 96 | runString(zen_module.c_str()); 97 | 98 | g_initialised = true; 99 | 100 | // Set the current profile if it's not xhtml (the default) 101 | if (g_fiProfileHtml == g_currentProfileIndex) 102 | { 103 | setProfile(_T("html"), g_fiProfileHtml); 104 | } 105 | else if (g_fiProfileXml == g_currentProfileIndex) 106 | { 107 | setProfile(_T("xml"), g_fiProfileXml); 108 | } 109 | else if (g_fiProfilePlain == g_currentProfileIndex) 110 | { 111 | setProfile(_T("plain"), g_fiProfilePlain); 112 | } 113 | 114 | 115 | 116 | } 117 | 118 | void doExpandAbbreviation() 119 | { 120 | CHECK_INITIALISED(); 121 | if (g_expandIsTab) 122 | { 123 | runString(_T("npp_zen_coding.expand_abbreviation(True)")); 124 | } 125 | else 126 | { 127 | runString(_T("npp_zen_coding.expand_abbreviation(False)")); 128 | } 129 | } 130 | 131 | 132 | 133 | void doWrapWithAbbreviation() 134 | { 135 | CHECK_INITIALISED(); 136 | runString(_T("npp_zen_coding.wrap_with_abbreviation()")); 137 | } 138 | 139 | 140 | void doNextEditPoint() 141 | { 142 | CHECK_INITIALISED(); 143 | runString(_T("npp_zen_coding.next_edit_point()")); 144 | } 145 | 146 | void doPreviousEditPoint() 147 | { 148 | CHECK_INITIALISED(); 149 | runString(_T("npp_zen_coding.prev_edit_point()")); 150 | } 151 | 152 | void doSelectNextItem() 153 | { 154 | CHECK_INITIALISED(); 155 | runString(_T("npp_zen_coding.select_next_item()")); 156 | } 157 | 158 | void doSelectPreviousItem() 159 | { 160 | CHECK_INITIALISED(); 161 | runString(_T("npp_zen_coding.select_previous_item()")); 162 | } 163 | 164 | void doMatchPairInward() 165 | { 166 | CHECK_INITIALISED(); 167 | runString(_T("npp_zen_coding.match_pair_inward()")); 168 | } 169 | 170 | void doMatchPairOutward() 171 | { 172 | CHECK_INITIALISED(); 173 | runString(_T("npp_zen_coding.match_pair_outward()")); 174 | } 175 | 176 | void doGoToMatchingPair() 177 | { 178 | CHECK_INITIALISED(); 179 | runString(_T("npp_zen_coding.go_to_matching_pair()")); 180 | } 181 | 182 | void doMergeLines() 183 | { 184 | CHECK_INITIALISED(); 185 | runString(_T("npp_zen_coding.merge_lines()")); 186 | } 187 | 188 | void doToggleComment() 189 | { 190 | CHECK_INITIALISED(); 191 | runString(_T("npp_zen_coding.toggle_comment()")); 192 | } 193 | 194 | void doSplitJoinTag() 195 | { 196 | CHECK_INITIALISED(); 197 | runString(_T("npp_zen_coding.split_join_tag()")); 198 | } 199 | 200 | void doRemoveTag() 201 | { 202 | CHECK_INITIALISED(); 203 | runString(_T("npp_zen_coding.remove_tag()")); 204 | } 205 | 206 | 207 | 208 | 209 | void doUpdateImageSize() 210 | { 211 | CHECK_INITIALISED(); 212 | runString(_T("npp_zen_coding.update_image_size()")); 213 | } 214 | 215 | void doAddEntryAbbreviation() 216 | { 217 | CHECK_INITIALISED(); 218 | runString(_T("npp_zen_coding.add_entry('abbreviations')")); 219 | } 220 | 221 | void doAddEntrySnippet() 222 | { 223 | CHECK_INITIALISED(); 224 | runString(_T("npp_zen_coding.add_entry('snippets')")); 225 | } 226 | 227 | void doEvaluateMathExpression() 228 | { 229 | CHECK_INITIALISED(); 230 | runString(_T("npp_zen_coding.evaluate_math_expression()")); 231 | } 232 | 233 | void doReflectCssValue() 234 | { 235 | CHECK_INITIALISED(); 236 | runString(_T("npp_zen_coding.reflect_css_value()")); 237 | } 238 | 239 | 240 | void doAutocomplete() 241 | { 242 | CHECK_INITIALISED(); 243 | runString(_T("npp_zen_coding.show_autocomplete()")); 244 | } 245 | 246 | void doAddAbbreviation() 247 | { 248 | CHECK_INITIALISED(); 249 | runString(_T("npp_zen_coding.add_entry('abbreviations')")); 250 | } 251 | 252 | void doAddSnippet() 253 | { 254 | CHECK_INITIALISED(); 255 | runString(_T("npp_zen_coding.add_entry('snippets')")); 256 | } 257 | 258 | void doAbout() 259 | { 260 | aboutDlg.doDialog(); 261 | } 262 | 263 | 264 | 265 | void setProfile(const TCHAR *profileName) 266 | { 267 | int profileIndex = -1; 268 | if (_tcscmp(profileName, _T("xhtml"))) 269 | { 270 | profileIndex = g_fiProfileXhtml; 271 | } 272 | else if (_tcscmp(profileName, _T("xml"))) 273 | { 274 | profileIndex = g_fiProfileXml; 275 | } 276 | else if (_tcscmp(profileName, _T("html"))) 277 | { 278 | profileIndex = g_fiProfileHtml; 279 | } 280 | else if (_tcscmp(profileName, _T("plain"))) 281 | { 282 | profileIndex = g_fiProfilePlain; 283 | } 284 | 285 | if (-1 != profileIndex) 286 | { 287 | setProfile(profileName, profileIndex); 288 | } 289 | } 290 | 291 | void setProfile(const TCHAR *profileName, int cmdIndex) 292 | { 293 | 294 | if (-1 != g_currentProfileIndex) 295 | { 296 | ::SendMessage(nppData._nppHandle, NPPM_SETMENUITEMCHECK, funcItem[g_currentProfileIndex]._cmdID, FALSE); 297 | } 298 | 299 | ::SendMessage(nppData._nppHandle, NPPM_SETMENUITEMCHECK, funcItem[cmdIndex]._cmdID, TRUE); 300 | 301 | if (g_initialised) 302 | { 303 | TCHAR cmd[150]; 304 | _tcscpy_s(cmd, 150, _T("npp_zen_coding.set_profile('")); 305 | _tcscat_s(cmd, 150, profileName); 306 | _tcscat_s(cmd, 150, _T("')")); 307 | 308 | runString(cmd); 309 | } 310 | g_currentProfile = profileName; 311 | g_currentProfileIndex = cmdIndex; 312 | } 313 | 314 | void doProfileAutoSelect() 315 | { 316 | g_autoSelectProfile = !g_autoSelectProfile; 317 | ::SendMessage(nppData._nppHandle, NPPM_SETMENUITEMCHECK, funcItem[g_fiAutoProfile]._cmdID, g_autoSelectProfile ? TRUE : FALSE); 318 | saveSettings(); 319 | } 320 | 321 | void doProfileXhtml() 322 | { 323 | setProfile(_T("xhtml"), g_fiProfileXhtml); 324 | } 325 | 326 | void doProfileHtml() 327 | { 328 | setProfile(_T("html"), g_fiProfileHtml); 329 | } 330 | 331 | void doProfileXml() 332 | { 333 | setProfile(_T("xml"), g_fiProfileXml); 334 | } 335 | 336 | void doProfilePlain() 337 | { 338 | setProfile(_T("plain"), g_fiProfilePlain); 339 | } 340 | 341 | 342 | 343 | 344 | BOOL APIENTRY DllMain( HMODULE hModule, 345 | DWORD ul_reason_for_call, 346 | LPVOID lpReserved 347 | ) 348 | { 349 | g_hModule = hModule; 350 | 351 | switch (ul_reason_for_call) 352 | { 353 | case DLL_PROCESS_ATTACH: 354 | 355 | break; 356 | 357 | case DLL_THREAD_ATTACH: 358 | case DLL_THREAD_DETACH: 359 | case DLL_PROCESS_DETACH: 360 | break; 361 | } 362 | return TRUE; 363 | } 364 | 365 | 366 | extern "C" __declspec(dllexport) void setInfo(NppData notepadPlusData) 367 | { 368 | nppData = notepadPlusData; 369 | aboutDlg.init(static_cast(g_hModule), nppData); 370 | 371 | ::SendMessage(nppData._nppHandle, NPPM_GETPLUGINSCONFIGDIR, MAX_PATH, reinterpret_cast(g_iniPath)); 372 | _tcscat_s(g_iniPath, MAX_PATH, _T("\\ZenCodingPython.ini")); 373 | } 374 | 375 | 376 | extern "C" __declspec(dllexport) CONST TCHAR * getName() 377 | { 378 | return PLUGIN_NAME; 379 | } 380 | 381 | 382 | extern "C" __declspec(dllexport) FuncItem * getFuncsArray(int *nbF) 383 | { 384 | 385 | 386 | 387 | 388 | if (g_funcItemManager != NULL) 389 | { 390 | delete g_funcItemManager; 391 | } 392 | g_funcItemManager = new FuncItemManager(); 393 | 394 | 395 | g_funcItemManager->addFunction(_T("Expand abbreviation"), doExpandAbbreviation, VK_RETURN, MODIFIER_CTRL | MODIFIER_ALT, false); 396 | g_funcItemManager->addFunction(_T("Wrap with abbreviation"), doWrapWithAbbreviation, VK_RETURN, MODIFIER_CTRL | MODIFIER_ALT | MODIFIER_SHIFT, false); 397 | g_funcItemManager->addSplitter(); // ---------------------- 398 | g_funcItemManager->addFunction(_T("Next edit point"), doNextEditPoint, VK_RIGHT, MODIFIER_CTRL | MODIFIER_ALT, false); 399 | g_funcItemManager->addFunction(_T("Previous edit point"), doPreviousEditPoint, VK_LEFT, MODIFIER_CTRL | MODIFIER_ALT, false); 400 | g_funcItemManager->addFunction(_T("Select next item"), doSelectNextItem); 401 | g_funcItemManager->addFunction(_T("Select previous item"), doSelectPreviousItem); 402 | g_funcItemManager->addSplitter(); // ---------------------- 403 | g_funcItemManager->addFunction(_T("Match pair inward"), doMatchPairInward); 404 | g_funcItemManager->addFunction(_T("Match pair outward"), doMatchPairOutward); 405 | g_funcItemManager->addFunction(_T("Go to matching pair"), doGoToMatchingPair); 406 | g_funcItemManager->addSplitter(); // ---------------------- 407 | g_funcItemManager->addFunction(_T("Split / join tag"), doSplitJoinTag); 408 | g_funcItemManager->addFunction(_T("Remove tag"), doRemoveTag); 409 | g_funcItemManager->addSplitter(); // ---------------------- 410 | g_funcItemManager->addFunction(_T("Toggle comment"), doToggleComment); 411 | g_funcItemManager->addFunction(_T("Update image size"), doUpdateImageSize); 412 | g_funcItemManager->addFunction(_T("Reflect CSS value"), doReflectCssValue); 413 | g_funcItemManager->addFunction(_T("Evalute math expression"), doEvaluateMathExpression); 414 | g_funcItemManager->addSplitter(); // ---------------------- 415 | g_funcItemManager->addFunction(_T("Add abbreviation"), doAddAbbreviation); 416 | g_funcItemManager->addFunction(_T("Add snippet"), doAddSnippet); 417 | g_funcItemManager->addSplitter(); // ---------------------- 418 | g_funcItemManager->addFunction(_T("Autocomplete"), doAutocomplete); 419 | g_funcItemManager->addSplitter(); // ---------------------- 420 | g_fiAutoProfile = g_funcItemManager->addFunction(_T("Auto select profile"), doProfileAutoSelect, NULL, true); 421 | g_fiProfileXhtml = g_funcItemManager->addFunction(_T("Profile: xhtml"), doProfileXhtml, NULL, true); 422 | g_fiProfileHtml = g_funcItemManager->addFunction(_T("Profile: html"), doProfileHtml); 423 | g_fiProfileXml = g_funcItemManager->addFunction(_T("Profile: xml"), doProfileXml); 424 | g_fiProfilePlain = g_funcItemManager->addFunction(_T("Profile: plain"), doProfilePlain); 425 | g_funcItemManager->addSplitter(); // ---------------------- 426 | g_funcItemManager->addFunction(_T("About"), doAbout); 427 | 428 | funcItem = g_funcItemManager->getFuncItems(nbF); 429 | return funcItem; 430 | } 431 | 432 | 433 | 434 | 435 | extern "C" __declspec(dllexport) void beNotified(SCNotification *notifyCode) 436 | { 437 | 438 | switch(notifyCode->nmhdr.code) 439 | { 440 | case NPPN_READY: 441 | { 442 | ShortcutKey key; 443 | ::SendMessage(nppData._nppHandle, NPPM_GETSHORTCUTBYCMDID, funcItem[0]._cmdID, reinterpret_cast(&key)); 444 | g_expandIsTab = keyIsTab(key); 445 | loadSettings(); 446 | // Lazy initialisation, so don't call initialise() yet 447 | //initialise(); 448 | } 449 | break; 450 | 451 | case NPPN_SHORTCUTREMAPPED: 452 | if (funcItem[0]._cmdID == notifyCode->nmhdr.idFrom) 453 | { 454 | g_expandIsTab = keyIsTab(*reinterpret_cast(notifyCode->nmhdr.hwndFrom)); 455 | } 456 | break; 457 | 458 | case NPPN_BUFFERACTIVATED: 459 | case NPPN_LANGCHANGED: 460 | if (g_autoSelectProfile) 461 | { 462 | int lang = ::SendMessage(nppData._nppHandle, NPPM_GETBUFFERLANGTYPE, notifyCode->nmhdr.idFrom, 0); 463 | switch(lang) 464 | { 465 | case L_XML: 466 | setProfile(_T("xml"), g_fiProfileXml); 467 | break; 468 | 469 | case L_TXT: 470 | setProfile(_T("plain"), g_fiProfilePlain); 471 | break; 472 | 473 | case L_HTML: 474 | default: 475 | setProfile(_T("xhtml"), g_fiProfileXhtml); 476 | break; 477 | } 478 | } 479 | break; 480 | case NPPN_FILESAVED: 481 | if (g_watchSave) 482 | { 483 | TCHAR filename[MAX_PATH]; 484 | ::SendMessage(nppData._nppHandle, NPPM_GETFULLPATHFROMBUFFERID, notifyCode->nmhdr.idFrom, reinterpret_cast(filename)); 485 | if (0 == _tcsicmp(filename, g_settingsFile)) 486 | { 487 | if (g_initialised) 488 | { 489 | runString(_T("npp_zen_coding.update_settings()")); 490 | ::MessageBox(nppData._nppHandle, _T("Zen Coding settings automatically refreshed"), _T("Zen Coding for Notepad++"), MB_ICONINFORMATION); 491 | } 492 | else 493 | { 494 | ::MessageBox(nppData._nppHandle, _T("New Zen Coding settings have been applied"), _T("Zen Coding for Notepad++"), MB_ICONINFORMATION); 495 | } 496 | } 497 | } 498 | break; 499 | } 500 | 501 | 502 | } 503 | 504 | extern "C" __declspec(dllexport) LRESULT messageProc(UINT message, WPARAM wParam , LPARAM lParam) 505 | { 506 | return TRUE; 507 | } 508 | 509 | 510 | #ifdef _UNICODE 511 | extern "C" __declspec(dllexport) BOOL isUnicode() 512 | { 513 | return TRUE; 514 | } 515 | #endif 516 | 517 | 518 | void runScript(TCHAR *str) 519 | { 520 | runString(str, PYSCR_EXECSCRIPT); 521 | } 522 | 523 | 524 | 525 | 526 | void runString(const TCHAR *script, int messageType /* = PYSCR_EXECSTATEMENT */) 527 | { 528 | if (g_pythonFailure) 529 | return; 530 | 531 | PythonScript_Exec pse; 532 | pse.structVersion = 1; 533 | 534 | pse.completedEvent = NULL; 535 | pse.deliverySuccess = FALSE; 536 | pse.flags = 0; 537 | pse.script = script; 538 | 539 | 540 | 541 | TCHAR pluginName[] = _T("PythonScript.dll"); 542 | CommunicationInfo commInfo; 543 | commInfo.internalMsg = messageType; 544 | commInfo.srcModuleName = PLUGIN_NAME; 545 | 546 | commInfo.info = reinterpret_cast(&pse); 547 | // SendMessage(nppData._nppHandle, NPPM_MSGTOPLUGIN, reinterpret_cast(pluginName), reinterpret_cast(&commInfo)); 548 | 549 | BOOL delivery = SendMessage(nppData._nppHandle, NPPM_MSGTOPLUGIN, reinterpret_cast(pluginName), reinterpret_cast(&commInfo)); 550 | if (!delivery) 551 | { 552 | MessageBox(NULL, _T("Python Script Plugin not found. Please install the Python Script plugin from Plugin Manager"), _T("Zen Coding - Python"), 0); 553 | g_pythonFailure = true; 554 | } 555 | else if (!pse.deliverySuccess) 556 | { 557 | MessageBox(NULL, _T("Python Script Plugin did not accept the script"), _T("Zen Coding - Python"), 0); 558 | g_pythonFailure = true; 559 | } 560 | } 561 | 562 | bool keyIsTab(const ShortcutKey& key) 563 | { 564 | if (key._key == VK_TAB && (key._isAlt == false && key._isCtrl == false && key._isShift == false)) 565 | { 566 | return true; 567 | } 568 | else 569 | { 570 | return false; 571 | } 572 | 573 | } 574 | 575 | 576 | void saveSettings() 577 | { 578 | ::WritePrivateProfileString(_T("Settings"), _T("AutoProfile"), g_autoSelectProfile ? _T("1") : _T("0"), g_iniPath); 579 | ::WritePrivateProfileString(_T("Settings"), _T("CurrentProfile"), g_currentProfile.c_str(), g_iniPath); 580 | } 581 | 582 | void loadSettings() 583 | { 584 | int result = ::GetPrivateProfileInt(_T("Settings"), _T("AutoProfile"), 1, g_iniPath); 585 | g_autoSelectProfile = (result == 1); 586 | if (!g_autoSelectProfile) 587 | { 588 | TCHAR tmp[20]; 589 | ::GetPrivateProfileString(_T("Settings"), _T("CurrentProfile"), _T("xhtml"), tmp, 20, g_iniPath); 590 | g_currentProfile = tmp; 591 | setProfile(g_currentProfile.c_str()); 592 | } 593 | ::SendMessage(nppData._nppHandle, NPPM_SETMENUITEMCHECK, funcItem[g_fiAutoProfile]._cmdID, g_autoSelectProfile ? TRUE : FALSE); 594 | 595 | } 596 | -------------------------------------------------------------------------------- /ZenCoding-Python.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruderstein/ZenCoding-Python/d291792c6ab154e6ffe41781eab222a0eaeccbaf/ZenCoding-Python.rc -------------------------------------------------------------------------------- /ZenCoding-Python.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZenCoding-Python", "ZenCoding-Python.vcxproj", "{80FF6833-2672-4C8A-9943-827824BA599E}" 5 | EndProject 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NppPlugin", "..\PythonScript\NppPlugin\project\NppPlugin.vcxproj", "{69CC76EB-0183-4622-929C-02E860A66A23}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug-ANSI|Win32 = Debug-ANSI|Win32 12 | Release|Win32 = Release|Win32 13 | Release-ANSI|Win32 = Release-ANSI|Win32 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {80FF6833-2672-4C8A-9943-827824BA599E}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {80FF6833-2672-4C8A-9943-827824BA599E}.Debug|Win32.Build.0 = Debug|Win32 18 | {80FF6833-2672-4C8A-9943-827824BA599E}.Debug-ANSI|Win32.ActiveCfg = Debug|Win32 19 | {80FF6833-2672-4C8A-9943-827824BA599E}.Debug-ANSI|Win32.Build.0 = Debug|Win32 20 | {80FF6833-2672-4C8A-9943-827824BA599E}.Release|Win32.ActiveCfg = Release|Win32 21 | {80FF6833-2672-4C8A-9943-827824BA599E}.Release|Win32.Build.0 = Release|Win32 22 | {80FF6833-2672-4C8A-9943-827824BA599E}.Release-ANSI|Win32.ActiveCfg = Release|Win32 23 | {80FF6833-2672-4C8A-9943-827824BA599E}.Release-ANSI|Win32.Build.0 = Release|Win32 24 | {69CC76EB-0183-4622-929C-02E860A66A23}.Debug|Win32.ActiveCfg = Debug|Win32 25 | {69CC76EB-0183-4622-929C-02E860A66A23}.Debug|Win32.Build.0 = Debug|Win32 26 | {69CC76EB-0183-4622-929C-02E860A66A23}.Debug-ANSI|Win32.ActiveCfg = Debug-ANSI|Win32 27 | {69CC76EB-0183-4622-929C-02E860A66A23}.Debug-ANSI|Win32.Build.0 = Debug-ANSI|Win32 28 | {69CC76EB-0183-4622-929C-02E860A66A23}.Release|Win32.ActiveCfg = Release|Win32 29 | {69CC76EB-0183-4622-929C-02E860A66A23}.Release|Win32.Build.0 = Release|Win32 30 | {69CC76EB-0183-4622-929C-02E860A66A23}.Release-ANSI|Win32.ActiveCfg = Release-ANSI|Win32 31 | {69CC76EB-0183-4622-929C-02E860A66A23}.Release-ANSI|Win32.Build.0 = Release-ANSI|Win32 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /ZenCoding-Python.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {80FF6833-2672-4C8A-9943-827824BA599E} 15 | Win32Proj 16 | ZenCodingPython 17 | 18 | 19 | 20 | DynamicLibrary 21 | true 22 | Unicode 23 | 24 | 25 | DynamicLibrary 26 | false 27 | true 28 | Unicode 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | true 42 | 43 | 44 | false 45 | 46 | 47 | 48 | Use 49 | Level3 50 | Disabled 51 | WIN32;_DEBUG;_WINDOWS;_USRDLL;ZENCODINGPYTHON_EXPORTS;%(PreprocessorDefinitions) 52 | E:\work\PythonScript\NppPlugin\include;E:\work\PythonScript\PythonScript\include;%(AdditionalIncludeDirectories) 53 | MultiThreadedDebug 54 | 55 | 56 | Windows 57 | true 58 | NppPlugin.lib;shlwapi.lib;%(AdditionalDependencies) 59 | E:\work\PythonScript\NppPlugin\bin\Debug;%(AdditionalLibraryDirectories) 60 | 61 | 62 | copy "$(OutputPath)$(TargetFileName)" "c:\program files (x86)\Notepad++5.8\plugins" 63 | 64 | 65 | 66 | 67 | Level3 68 | Use 69 | MaxSpeed 70 | true 71 | true 72 | WIN32;NDEBUG;_WINDOWS;_USRDLL;ZENCODINGPYTHON_EXPORTS;%(PreprocessorDefinitions) 73 | E:\work\PythonScript\NppPlugin\include;E:\work\PythonScript\PythonScript\include;%(AdditionalIncludeDirectories) 74 | MultiThreaded 75 | 76 | 77 | Windows 78 | true 79 | true 80 | true 81 | NppPlugin.lib;shlwapi.lib;%(AdditionalDependencies) 82 | E:\work\PythonScript\NppPlugin\bin\Release;%(AdditionalLibraryDirectories) 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | false 100 | 101 | 102 | false 103 | 104 | 105 | 106 | 107 | Create 108 | Create 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /ZenCoding-Python.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | 38 | 39 | Source Files 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | Source Files 49 | 50 | 51 | 52 | 53 | Resource Files 54 | 55 | 56 | -------------------------------------------------------------------------------- /ZenCodingPython/npp_zen_coding.py: -------------------------------------------------------------------------------- 1 | import Npp 2 | 3 | import os 4 | import pickle 5 | 6 | import zencoding 7 | import zencoding.zen_editor 8 | 9 | _npp_editor = zencoding.zen_editor.ZenEditor() 10 | 11 | _profile = 'xhtml' 12 | _full_list = {} 13 | 14 | _originalSeparator = 32 15 | _initialAutoCompleteLength = 0 16 | 17 | 18 | def expand_abbreviation(isTab): 19 | global _profile 20 | result = zencoding.run_action('expand_abbreviation', _npp_editor, None, _profile) 21 | if isTab and result == False: 22 | Npp.editor.tab() 23 | 24 | def wrap_with_abbreviation(): 25 | global _profile 26 | abbr = Npp.notepad.prompt("Wrap with abbreviation:", "Zen Coding - Python") 27 | if abbr is not None: 28 | zencoding.run_action('wrap_with_abbreviation', _npp_editor, abbr, None, _profile) 29 | 30 | def next_edit_point(): 31 | zencoding.run_action('next_edit_point', _npp_editor) 32 | 33 | 34 | def prev_edit_point(): 35 | zencoding.run_action('prev_edit_point', _npp_editor) 36 | 37 | def select_next_item(): 38 | zencoding.run_action('select_next_item', _npp_editor) 39 | 40 | def select_previous_item(): 41 | zencoding.run_action('select_previous_item', _npp_editor) 42 | 43 | 44 | def match_pair_inward(): 45 | zencoding.run_action('match_pair_inward', _npp_editor) 46 | 47 | 48 | def match_pair_outward(): 49 | zencoding.run_action('match_pair_outward', _npp_editor) 50 | 51 | 52 | def go_to_matching_pair(): 53 | zencoding.run_action('go_to_matching_pair', _npp_editor) 54 | 55 | def merge_lines(): 56 | zencoding.run_action('merge_lines', _npp_editor) 57 | 58 | 59 | def toggle_comment(): 60 | zencoding.run_action('toggle_comment', _npp_editor) 61 | 62 | 63 | def split_join_tag(): 64 | zencoding.run_action('split_join_tag', _npp_editor) 65 | 66 | 67 | def remove_tag(): 68 | zencoding.run_action('remove_tag', _npp_editor) 69 | 70 | 71 | def reflect_css_value(): 72 | zencoding.run_action('reflect_css_value', _npp_editor) 73 | 74 | def evaluate_math_expression(): 75 | zencoding.run_action('evaluate_math_expression', _npp_editor) 76 | 77 | def increment_number(step): 78 | zencoding.run_action('increment_number', _npp_editor, step) 79 | 80 | def update_image_size(): 81 | zencoding.run_action('update_image_size', _npp_editor) 82 | 83 | def add_entry(type): 84 | text = Npp.editor.getSelText() 85 | if not text: 86 | Npp.notepad.messageBox('Please highlight some text to use as the {0}'.format(type[:-1]), 'Zen Coding - Python') 87 | return 88 | 89 | user_vocab = zencoding.resources.get_vocabulary('user') 90 | abbrev = Npp.notepad.prompt('Enter {0}:'.format(type[:-1]), 'Zen Coding Add Entry') 91 | mode = _npp_editor.get_syntax() 92 | 93 | if abbrev: 94 | if mode not in user_vocab: 95 | user_vocab[mode] = {} 96 | if type not in user_vocab[mode]: 97 | user_vocab[mode][type] = {} 98 | 99 | user_vocab[mode][type][abbrev] = text 100 | _create_autocomplete_list() 101 | try: 102 | save_user_settings() 103 | Npp.notepad.messageBox('{0} "{1}" added and saved'.format(type[0].upper() + type[1:-1], abbrev), 'Zen Coding') 104 | except: 105 | Npp.notepad.messageBox('{0} "{1}" added but error saving user settings'.format(type[0].upper() + type[1:-1], abbrev), 'Zen Coding') 106 | 107 | 108 | def _get_autocomplete_list_for_lang(lang): 109 | system_vocab = zencoding.resources.get_vocabulary('system') 110 | user_vocab = zencoding.resources.get_vocabulary('user') 111 | lang_list = [] 112 | if lang in system_vocab: 113 | if 'abbreviations' in system_vocab[lang]: 114 | lang_list = [x for x in system_vocab[lang]['abbreviations']] 115 | 116 | if 'snippets' in system_vocab[lang]: 117 | lang_list.extend([x for x in system_vocab[lang]['snippets']]) 118 | 119 | 120 | if lang in user_vocab: 121 | if 'abbreviations' in user_vocab[lang]: 122 | lang_list.extend([x for x in user_vocab[lang]['abbreviations']]) 123 | 124 | if 'snippets' in user_vocab[lang]: 125 | lang_list.extend([x for x in user_vocab[lang]['snippets']]) 126 | 127 | if lang in system_vocab and 'extends' in system_vocab[lang]: 128 | if type(system_vocab[lang]['extends']).__name__ == 'str': 129 | system_vocab[lang]['extends'] = system_vocab[lang]['extends'].split(',') 130 | 131 | for extendslang in system_vocab[lang]['extends']: 132 | lang_list.extend(_get_autocomplete_list_for_lang(extendslang)) 133 | 134 | if lang in user_vocab and 'extends' in user_vocab[lang]: 135 | if type(user_vocab[lang]['extends']).__name__ == 'str': 136 | user_vocab[lang]['extends'] = user_vocab[lang]['extends'].split(',') 137 | 138 | for extendslang in user_vocab[lang]['extends']: 139 | if system_vocab[lang]['extends'] and extendslang not in system_vocab[lang]['extends'].split(','): 140 | lang_list.extend(_get_autocomplete_list_for_lang(extendslang)) 141 | 142 | return lang_list 143 | 144 | 145 | def _create_autocomplete_list(): 146 | global _full_list 147 | system_vocab = zencoding.resources.get_vocabulary('system') 148 | user_vocab = zencoding.resources.get_vocabulary('user') 149 | 150 | for lang in system_vocab: 151 | _full_list[lang] = _get_autocomplete_list_for_lang(lang) 152 | _full_list[lang].sort() 153 | 154 | 155 | for lang in user_vocab: 156 | if lang not in system_vocab: 157 | _full_list[lang] = _get_autocomplete_list_for_lang(lang) 158 | _full_list[lang].sort() 159 | 160 | 161 | def _get_user_file(): 162 | return os.path.dirname(os.path.abspath( __file__ )) + '\\user_settings.pickle' 163 | 164 | 165 | def load_user_settings(): 166 | try: 167 | f = open(_get_user_file(), 'rb') 168 | except: 169 | f = None 170 | pass 171 | if f: 172 | user_vocab = pickle.load(f) 173 | f.close() 174 | zencoding.resources.set_vocabulary(user_vocab, 'user') 175 | 176 | _create_autocomplete_list() 177 | 178 | 179 | def save_user_settings(): 180 | user_vocab = zencoding.resources.get_vocabulary('user') 181 | f = open(_get_user_file(), 'wb') 182 | pickle.dump(user_vocab, f) 183 | f.close() 184 | 185 | def _get_autocomplete_list(syntax, start): 186 | startlen = len(start) 187 | return [x for x in _full_list[syntax] if x[:startlen] == start] 188 | 189 | 190 | def _get_autocomplete_leader(): 191 | pos = Npp.editor.getCurrentPos() 192 | lineStart = Npp.editor.positionFromLine(Npp.editor.lineFromPosition(pos)) 193 | begin = '' 194 | for pos in range(pos - 1, lineStart - 1, -1): 195 | c = chr(Npp.editor.getCharAt(pos)) 196 | if c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:_-': 197 | begin = c + begin 198 | else: 199 | break 200 | return begin 201 | 202 | 203 | def _handle_selection(args): 204 | Npp.editor.clearCallbacks(_handle_selection) 205 | Npp.editor.clearCallbacks(_handle_cancel) 206 | Npp.editor.clearCallbacks(_handle_charadded) 207 | 208 | 209 | def _handle_cancel(args = None): 210 | Npp.editor.clearCallbacks(_handle_selection) 211 | Npp.editor.clearCallbacks(_handle_cancel) 212 | Npp.editor.clearCallbacks(_handle_charadded) 213 | 214 | 215 | def _handle_charadded(args): 216 | _handle_cancel() 217 | Npp.editor.autoCCancel() 218 | show_autocomplete() 219 | 220 | 221 | 222 | def show_autocomplete(): 223 | global _originalSeparator, _initialAutoCompleteLength 224 | 225 | begin = _get_autocomplete_leader() 226 | _originalSeparator = Npp.editor.autoCGetSeparator() 227 | Npp.editor.autoCSetSeparator(ord('\n')) 228 | _initialAutoCompleteLength = len(begin) 229 | autolist = _get_autocomplete_list(_npp_editor.get_syntax(), begin) 230 | 231 | Npp.editor.autoCSetCancelAtStart(False) 232 | Npp.editor.autoCSetFillUps(">+{[(") 233 | Npp.editor.callback(_handle_selection, [Npp.SCINTILLANOTIFICATION.AUTOCSELECTION]) 234 | Npp.editor.callback(_handle_cancel, [Npp.SCINTILLANOTIFICATION.AUTOCCANCELLED]) 235 | Npp.editor.callback(_handle_charadded, [Npp.SCINTILLANOTIFICATION.CHARADDED, Npp.SCINTILLANOTIFICATION.AUTOCCHARDELETED]) 236 | Npp.editor.autoCShow(_initialAutoCompleteLength, "\n".join(autolist)) 237 | 238 | def set_profile(profile): 239 | global _profile 240 | _profile = profile 241 | 242 | def reflect_css_value(): 243 | zencoding.run_action('reflect_css_value', _npp_editor) 244 | 245 | 246 | load_user_settings() 247 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/__init__.py: -------------------------------------------------------------------------------- 1 | import utils 2 | import re 3 | import os 4 | import imp 5 | 6 | __actions = {} 7 | __filters = {} 8 | __imported = [] 9 | 10 | def action(name=None, action_func=None): 11 | "Decorator for Zen Coding actions" 12 | if name == None and action_func == None: 13 | # @zencoding.action() 14 | return action_function 15 | elif action_func == None: 16 | if(callable(name)): 17 | # @zencoding.action 18 | return action_function(name) 19 | else: 20 | # @zencoding.action('somename') or @zencoding.action(name='somename') 21 | def dec(func): 22 | return action(name, func) 23 | return dec 24 | elif name != None and action_func != None: 25 | # zencoding.action('somename', somefunc) 26 | __actions[name] = action_func 27 | return action_func 28 | else: 29 | raise "Unsupported arguments to Zen Action: (%r, %r)", (name, action_func) 30 | 31 | def action_function(func): 32 | __actions[getattr(func, "_decorated_function", func).__name__] = func 33 | return func 34 | 35 | def filter(name=None, filter_func=None): 36 | "Decorator for Zen Coding filters" 37 | if name == None and filter_func == None: 38 | # @zencoding.filter() 39 | return filter_function 40 | elif filter_func == None: 41 | if(callable(name)): 42 | # @zencoding.filter 43 | return filter_function(name) 44 | else: 45 | # @zencoding.filter('somename') or @zencoding.filter(name='somename') 46 | def dec(func): 47 | return filter(name, func) 48 | return dec 49 | elif name != None and filter_func != None: 50 | # zencoding.filter('somename', somefunc) 51 | __filters[name] = filter_func 52 | return filter_func 53 | else: 54 | raise "Unsupported arguments to Zen Filter: (%r, %r)", (name, filter_func) 55 | 56 | def filter_function(func): 57 | __filters[getattr(func, "_decorated_function", func).__name__] = func 58 | return func 59 | 60 | def run_action(name, *args, **kwargs): 61 | """ 62 | Runs Zen Coding action. For list of available actions and their 63 | arguments see zen_actions.py file. 64 | @param name: Action name 65 | @type name: str 66 | @param args: Additional arguments. It may be array of arguments 67 | or inline arguments. The first argument should be zen_editor instance 68 | @type args: list 69 | @example 70 | zencoding.run_actions('expand_abbreviation', zen_editor) 71 | zencoding.run_actions('wrap_with_abbreviation', zen_editor, 'div') 72 | """ 73 | import zencoding.actions 74 | if name in __actions: 75 | return __actions[name](*args, **kwargs) 76 | 77 | return False 78 | 79 | def run_filters(tree, profile, filter_list): 80 | """ 81 | Runs filters on tree 82 | @type tree: ZenNode 83 | @param profile: str, object 84 | @param filter_list: str, list 85 | @return: ZenNode 86 | """ 87 | import zencoding.filters 88 | 89 | profile = utils.process_profile(profile) 90 | 91 | if isinstance(filter_list, basestring): 92 | filter_list = re.split(r'[\|,]', filter_list) 93 | 94 | for name in filter_list: 95 | name = name.strip() 96 | if name and name in __filters: 97 | tree = __filters[name](tree, profile) 98 | 99 | return tree 100 | 101 | def expand_abbreviation(abbr, syntax='html', profile_name='plain'): 102 | """ 103 | Expands abbreviation into a XHTML tag string 104 | @type abbr: str 105 | @return: str 106 | """ 107 | tree_root = utils.parse_into_tree(abbr, syntax) 108 | if tree_root: 109 | tree = utils.rollout_tree(tree_root) 110 | utils.apply_filters(tree, syntax, profile_name, tree_root.filters) 111 | return utils.replace_variables(tree.to_string()) 112 | 113 | return '' 114 | 115 | def wrap_with_abbreviation(abbr, text, syntax='html', profile='plain'): 116 | """ 117 | Wraps passed text with abbreviation. Text will be placed inside last 118 | expanded element 119 | @param abbr: Abbreviation 120 | @type abbr: str 121 | 122 | @param text: Text to wrap 123 | @type text: str 124 | 125 | @param syntax: Document type (html, xml, etc.) 126 | @type syntax: str 127 | 128 | @param profile: Output profile's name. 129 | @type profile: str 130 | @return {String} 131 | """ 132 | tree_root = utils.parse_into_tree(abbr, syntax) 133 | pasted = False 134 | 135 | if tree_root: 136 | if tree_root.multiply_elem: 137 | # we have a repeating element, put content in 138 | tree_root.multiply_elem.set_paste_content(text) 139 | tree_root.multiply_elem.repeat_by_lines = pasted = True 140 | 141 | 142 | tree = utils.rollout_tree(tree_root) 143 | 144 | if not pasted: 145 | tree.paste_content(text) 146 | 147 | utils.apply_filters(tree, syntax, profile, tree_root.filters) 148 | return utils.replace_variables(tree.to_string()) 149 | 150 | return None -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/actions/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # import all actions 5 | import os 6 | __sub_modules = [] 7 | 8 | for file in os.listdir(os.path.dirname(__file__)): 9 | name, ext = os.path.splitext(file) 10 | if ext.lower() == '.py': 11 | __sub_modules.append(name) 12 | 13 | __import__(__name__, globals(), locals(), __sub_modules) 14 | 15 | del __sub_modules -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/actions/basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Middleware layer that communicates between editor and Zen Coding. 6 | This layer describes all available Zen Coding actions, like 7 | "Expand Abbreviation". 8 | @author Sergey Chikuyonok (serge.che@gmail.com) 9 | @link http://chikuyonok.ru 10 | """ 11 | import zencoding.utils 12 | import zencoding.html_matcher as html_matcher 13 | import zencoding.interface.file as zen_file 14 | import base64 15 | import re 16 | 17 | mime_types = { 18 | 'gif': 'image/gif', 19 | 'png': 'image/png', 20 | 'jpg': 'image/jpeg', 21 | 'jpeg': 'image/jpeg', 22 | 'svg': 'image/svg+xml', 23 | 'html': 'text/html', 24 | 'htm': 'text/html' 25 | } 26 | 27 | def find_abbreviation(editor): 28 | """ 29 | Search for abbreviation in editor from current caret position 30 | @param editor: Editor instance 31 | @type editor: ZenEditor 32 | @return: str 33 | """ 34 | start, end = editor.get_selection_range() 35 | if start != end: 36 | # abbreviation is selected by user 37 | return editor.get_content()[start:end]; 38 | 39 | # search for new abbreviation from current caret position 40 | cur_line_start, cur_line_end = editor.get_current_line_range() 41 | return zencoding.utils.extract_abbreviation(editor.get_content()[cur_line_start:start]) 42 | 43 | @zencoding.action 44 | def expand_abbreviation(editor, syntax=None, profile_name=None): 45 | """ 46 | Find from current caret position and expand abbreviation in editor 47 | @param editor: Editor instance 48 | @type editor: ZenEditor 49 | @param syntax: Syntax type (html, css, etc.) 50 | @type syntax: str 51 | @param profile_name: Output profile name (html, xml, xhtml) 52 | @type profile_name: str 53 | @return: True if abbreviation was expanded successfully 54 | """ 55 | if syntax is None: syntax = editor.get_syntax() 56 | if profile_name is None: profile_name = editor.get_profile_name() 57 | 58 | range_start, caret_pos = editor.get_selection_range() 59 | abbr = find_abbreviation(editor) 60 | content = '' 61 | 62 | if abbr: 63 | content = zencoding.expand_abbreviation(abbr, syntax, profile_name) 64 | if content: 65 | editor.replace_content(content, caret_pos - len(abbr), caret_pos) 66 | return True 67 | 68 | return False 69 | 70 | @zencoding.action 71 | def expand_abbreviation_with_tab(editor, syntax, profile_name='xhtml'): 72 | """ 73 | A special version of expandAbbreviation function: if it can't 74 | find abbreviation, it will place Tab character at caret position 75 | @param editor: Editor instance 76 | @type editor: ZenEditor 77 | @param syntax: Syntax type (html, css, etc.) 78 | @type syntax: str 79 | @param profile_name: Output profile name (html, xml, xhtml) 80 | @type profile_name: str 81 | """ 82 | if not expand_abbreviation(editor, syntax, profile_name): 83 | editor.replace_content(zencoding.utils.get_variable('indentation'), editor.get_caret_pos()) 84 | 85 | return True 86 | 87 | @zencoding.action 88 | def match_pair(editor, direction='out', syntax=None): 89 | """ 90 | Find and select HTML tag pair 91 | @param editor: Editor instance 92 | @type editor: ZenEditor 93 | @param direction: Direction of pair matching: 'in' or 'out'. 94 | @type direction: str 95 | """ 96 | direction = direction.lower() 97 | if syntax is None: syntax = editor.get_profile_name() 98 | 99 | range_start, range_end = editor.get_selection_range() 100 | cursor = range_end 101 | content = editor.get_content() 102 | rng = None 103 | 104 | old_open_tag = html_matcher.last_match['opening_tag'] 105 | old_close_tag = html_matcher.last_match['closing_tag'] 106 | 107 | if direction == 'in' and old_open_tag and range_start != range_end: 108 | # user has previously selected tag and wants to move inward 109 | if not old_close_tag: 110 | # unary tag was selected, can't move inward 111 | return False 112 | elif old_open_tag.start == range_start: 113 | if content[old_open_tag.end] == '<': 114 | # test if the first inward tag matches the entire parent tag's content 115 | _r = html_matcher.find(content, old_open_tag.end + 1, syntax) 116 | if _r[0] == old_open_tag.end and _r[1] == old_close_tag.start: 117 | rng = html_matcher.match(content, old_open_tag.end + 1, syntax) 118 | else: 119 | rng = (old_open_tag.end, old_close_tag.start) 120 | else: 121 | rng = (old_open_tag.end, old_close_tag.start) 122 | else: 123 | new_cursor = content[0:old_close_tag.start].find('<', old_open_tag.end) 124 | search_pos = new_cursor + 1 if new_cursor != -1 else old_open_tag.end 125 | rng = html_matcher.match(content, search_pos, syntax) 126 | else: 127 | rng = html_matcher.match(content, cursor, syntax) 128 | 129 | if rng and rng[0] is not None: 130 | editor.create_selection(rng[0], rng[1]) 131 | return True 132 | else: 133 | return False 134 | 135 | @zencoding.action 136 | def match_pair_inward(editor): 137 | return match_pair(editor, 'in') 138 | 139 | @zencoding.action 140 | def match_pair_outward(editor): 141 | return match_pair(editor, 'out') 142 | 143 | def narrow_to_non_space(text, start, end): 144 | """ 145 | Narrow down text indexes, adjusting selection to non-space characters 146 | @type text: str 147 | @type start: int 148 | @type end: int 149 | @return: list 150 | """ 151 | # narrow down selection until first non-space character 152 | while start < end: 153 | if not text[start].isspace(): 154 | break 155 | 156 | start += 1 157 | 158 | while end > start: 159 | end -= 1 160 | if not text[end].isspace(): 161 | end += 1 162 | break 163 | 164 | return start, end 165 | 166 | @zencoding.action 167 | def wrap_with_abbreviation(editor, abbr=None, syntax=None, profile_name=None): 168 | """ 169 | Wraps content with abbreviation 170 | @param editor: Editor instance 171 | @type editor: ZenEditor 172 | @param syntax: Syntax type (html, css, etc.) 173 | @type syntax: str 174 | @param profile_name: Output profile name (html, xml, xhtml) 175 | @type profile_name: str 176 | """ 177 | if abbr is None: 178 | abbr = editor.prompt('Enter abbreviation') 179 | 180 | if not abbr: return None 181 | 182 | if syntax is None: syntax = editor.get_syntax() 183 | if profile_name is None: profile_name = editor.get_profile_name() 184 | 185 | start_offset, end_offset = editor.get_selection_range() 186 | content = editor.get_content() 187 | 188 | if start_offset == end_offset: 189 | # no selection, find tag pair 190 | rng = html_matcher.match(content, start_offset, profile_name) 191 | 192 | if rng[0] is None: # nothing to wrap 193 | return None 194 | else: 195 | start_offset, end_offset = rng 196 | 197 | start_offset, end_offset = narrow_to_non_space(content, start_offset, end_offset) 198 | line_bounds = get_line_bounds(content, start_offset) 199 | padding = zencoding.utils.get_line_padding(content[line_bounds[0]:line_bounds[1]]) 200 | 201 | new_content = zencoding.utils.escape_text(content[start_offset:end_offset]) 202 | result = zencoding.wrap_with_abbreviation(abbr, zencoding.utils.unindent_text(new_content, padding), syntax, profile_name) 203 | 204 | if result: 205 | editor.replace_content(result, start_offset, end_offset) 206 | return True 207 | 208 | return False 209 | 210 | def find_new_edit_point(editor, inc=1, offset=0): 211 | """ 212 | Search for new caret insertion point 213 | @param editor: Editor instance 214 | @type editor: ZenEditor 215 | @param inc: Search increment: -1 — search left, 1 — search right 216 | @param offset: Initial offset relative to current caret position 217 | @return: -1 if insertion point wasn't found 218 | """ 219 | cur_point = editor.get_caret_pos() + offset 220 | content = editor.get_content() 221 | max_len = len(content) 222 | next_point = -1 223 | re_empty_line = r'^\s+$' 224 | 225 | def get_line(ix): 226 | return editor.get_line_from_position(ix) 227 | #start = ix 228 | #while start >= 0: 229 | # c = content[start] 230 | # if c == '\n' or c == '\r': break 231 | # start -= 1 232 | 233 | #return content[start:ix] 234 | 235 | # Notepad++ change: 236 | cur_char = [''] 237 | next_char = [''] 238 | prev_char = [''] 239 | def next_chars_backwards(pt): 240 | next_char[0] = cur_char[0] 241 | cur_char[0] = prev_char[0] 242 | prev_char[0] = editor.char_at(pt - 1) 243 | 244 | def next_chars_forwards(pt): 245 | prev_char[0] = cur_char[0] 246 | cur_char[0] = next_char[0] 247 | next_char[0] = editor.char_at(pt + 1) 248 | 249 | if inc == -1: # Search backwards 250 | next_chars = next_chars_backwards 251 | cur_char[0] = editor.char_at(cur_point + 1) 252 | prev_char[0] = editor.char_at(cur_point) 253 | 254 | else: # Search forwards 255 | next_chars = next_chars_forwards 256 | cur_char[0]= editor.char_at(cur_point - 1) 257 | next_char[0] = editor.char_at(cur_point) 258 | 259 | 260 | 261 | while cur_point < max_len and cur_point > 0: 262 | next_chars(cur_point) 263 | cur_point += inc 264 | 265 | if cur_char[0] in '"\'': 266 | if next_char[0] == cur_char[0] and prev_char[0] == '=': 267 | # empty attribute 268 | next_point = cur_point + 1 - inc 269 | elif cur_char[0] == '>' and next_char[0] == '<': 270 | # between tags 271 | next_point = cur_point + 1 - inc 272 | elif cur_char[0] in '\r\n': 273 | # empty line 274 | if re.search(re_empty_line, get_line(cur_point)): 275 | next_point = cur_point 276 | 277 | if next_point != -1: break 278 | 279 | return next_point 280 | 281 | @zencoding.action 282 | def prev_edit_point(editor): 283 | """ 284 | Move caret to previous edit point 285 | @param editor: Editor instance 286 | @type editor: ZenEditor 287 | """ 288 | cur_pos = editor.get_caret_pos() 289 | new_point = find_new_edit_point(editor, -1) 290 | 291 | if new_point == cur_pos: 292 | # we're still in the same point, try searching from the other place 293 | new_point = find_new_edit_point(editor, -1, -2) 294 | 295 | if new_point != -1: 296 | editor.set_caret_pos(new_point) 297 | return True 298 | 299 | return False 300 | 301 | @zencoding.action 302 | def next_edit_point(editor): 303 | """ 304 | Move caret to next edit point 305 | @param editor: Editor instance 306 | @type editor: ZenEditor 307 | """ 308 | new_point = find_new_edit_point(editor, 1) 309 | if new_point != -1: 310 | editor.set_caret_pos(new_point) 311 | return True 312 | 313 | return False 314 | 315 | @zencoding.action 316 | def insert_formatted_newline(editor, mode='html'): 317 | """ 318 | Inserts newline character with proper indentation 319 | @param editor: Editor instance 320 | @type editor: ZenEditor 321 | @param mode: Syntax mode (only 'html' is implemented) 322 | @type mode: str 323 | """ 324 | caret_pos = editor.get_caret_pos() 325 | nl = zencoding.utils.get_newline() 326 | pad = zencoding.utils.get_variable('indentation') 327 | 328 | if mode == 'html': 329 | # let's see if we're breaking newly created tag 330 | pair = html_matcher.get_tags(editor.get_content(), editor.get_caret_pos(), editor.get_profile_name()) 331 | 332 | if pair[0] and pair[1] and pair[0].type == 'tag' and pair[0].end == caret_pos and pair[1].start == caret_pos: 333 | editor.replace_content(nl + pad + zencoding.utils.get_caret_placeholder() + nl, caret_pos) 334 | else: 335 | editor.replace_content(nl, caret_pos) 336 | else: 337 | editor.replace_content(nl, caret_pos) 338 | 339 | return True 340 | 341 | @zencoding.action 342 | def select_line(editor): 343 | """ 344 | Select line under cursor 345 | @param editor: Editor instance 346 | @type editor: ZenEditor 347 | """ 348 | start, end = editor.get_current_line_range(); 349 | editor.create_selection(start, end) 350 | return True 351 | 352 | @zencoding.action 353 | def go_to_matching_pair(editor): 354 | """ 355 | Moves caret to matching opening or closing tag 356 | @param editor: Editor instance 357 | @type editor: ZenEditor 358 | """ 359 | content = editor.get_content() 360 | caret_pos = editor.get_caret_pos() 361 | 362 | if content[caret_pos] == '<': 363 | # looks like caret is outside of tag pair 364 | caret_pos += 1 365 | 366 | tags = html_matcher.get_tags(content, caret_pos, editor.get_profile_name()) 367 | 368 | if tags and tags[0]: 369 | # match found 370 | open_tag, close_tag = tags 371 | 372 | if close_tag: # exclude unary tags 373 | if open_tag.start <= caret_pos and open_tag.end >= caret_pos: 374 | editor.set_caret_pos(close_tag.start) 375 | elif close_tag.start <= caret_pos and close_tag.end >= caret_pos: 376 | editor.set_caret_pos(open_tag.start) 377 | 378 | return True 379 | 380 | return False 381 | 382 | @zencoding.action 383 | def merge_lines(editor): 384 | """ 385 | Merge lines spanned by user selection. If there's no selection, tries to find 386 | matching tags and use them as selection 387 | @param editor: Editor instance 388 | @type editor: ZenEditor 389 | """ 390 | start, end = editor.get_selection_range() 391 | if start == end: 392 | # find matching tag 393 | pair = html_matcher.match(editor.get_content(), editor.get_caret_pos(), editor.get_profile_name()) 394 | if pair and pair[0] is not None: 395 | start, end = pair 396 | 397 | if start != end: 398 | # got range, merge lines 399 | text = editor.get_content()[start:end] 400 | lines = map(lambda s: re.sub(r'^\s+', '', s), zencoding.utils.split_by_lines(text)) 401 | text = re.sub(r'\s{2,}', ' ', ''.join(lines)) 402 | editor.replace_content(text, start, end) 403 | editor.create_selection(start, start + len(text)) 404 | return True 405 | 406 | return False 407 | 408 | @zencoding.action 409 | def toggle_comment(editor): 410 | """ 411 | Toggle comment on current editor's selection or HTML tag/CSS rule 412 | @type editor: ZenEditor 413 | """ 414 | syntax = editor.get_syntax() 415 | if syntax == 'css': 416 | # in case out editor is good enough and can recognize syntax from 417 | # current token, we have to make sure that cursor is not inside 418 | # 'style' attribute of html element 419 | caret_pos = editor.get_caret_pos() 420 | pair = html_matcher.get_tags(editor.get_content(),caret_pos) 421 | if pair and pair[0] and pair[0].type == 'tag' and pair[0].start <= caret_pos and pair[0].end >= caret_pos: 422 | syntax = 'html' 423 | 424 | if syntax == 'css': 425 | return toggle_css_comment(editor) 426 | else: 427 | return toggle_html_comment(editor) 428 | 429 | def toggle_html_comment(editor): 430 | """ 431 | Toggle HTML comment on current selection or tag 432 | @type editor: ZenEditor 433 | @return: True if comment was toggled 434 | """ 435 | start, end = editor.get_selection_range() 436 | content = editor.get_content() 437 | 438 | if start == end: 439 | # no selection, find matching tag 440 | pair = html_matcher.get_tags(content, editor.get_caret_pos(), editor.get_profile_name()) 441 | if pair and pair[0]: # found pair 442 | start = pair[0].start 443 | end = pair[1] and pair[1].end or pair[0].end 444 | 445 | return generic_comment_toggle(editor, '', start, end) 446 | 447 | def toggle_css_comment(editor): 448 | """ 449 | Simple CSS commenting 450 | @type editor: ZenEditor 451 | @return: True if comment was toggled 452 | """ 453 | start, end = editor.get_selection_range() 454 | 455 | if start == end: 456 | # no selection, get current line 457 | start, end = editor.get_current_line_range() 458 | 459 | # adjust start index till first non-space character 460 | start, end = narrow_to_non_space(editor.get_content(), start, end) 461 | 462 | return generic_comment_toggle(editor, '/*', '*/', start, end) 463 | 464 | def search_comment(text, pos, start_token, end_token): 465 | """ 466 | Search for nearest comment in str, starting from index from 467 | @param text: Where to search 468 | @type text: str 469 | @param pos: Search start index 470 | @type pos: int 471 | @param start_token: Comment start string 472 | @type start_token: str 473 | @param end_token: Comment end string 474 | @type end_token: str 475 | @return: None if comment wasn't found, list otherwise 476 | """ 477 | start_ch = start_token[0] 478 | end_ch = end_token[0] 479 | comment_start = -1 480 | comment_end = -1 481 | 482 | def has_match(tx, start): 483 | return text[start:start + len(tx)] == tx 484 | 485 | 486 | # search for comment start 487 | while pos: 488 | pos -= 1 489 | if text[pos] == start_ch and has_match(start_token, pos): 490 | comment_start = pos 491 | break 492 | 493 | if comment_start != -1: 494 | # search for comment end 495 | pos = comment_start 496 | content_len = len(text) 497 | while content_len >= pos: 498 | pos += 1 499 | if text[pos] == end_ch and has_match(end_token, pos): 500 | comment_end = pos + len(end_token) 501 | break 502 | 503 | if comment_start != -1 and comment_end != -1: 504 | return comment_start, comment_end 505 | else: 506 | return None 507 | 508 | def generic_comment_toggle(editor, comment_start, comment_end, range_start, range_end): 509 | """ 510 | Generic comment toggling routine 511 | @type editor: ZenEditor 512 | @param comment_start: Comment start token 513 | @type comment_start: str 514 | @param comment_end: Comment end token 515 | @type comment_end: str 516 | @param range_start: Start selection range 517 | @type range_start: int 518 | @param range_end: End selection range 519 | @type range_end: int 520 | @return: bool 521 | """ 522 | content = editor.get_content() 523 | caret_pos = [editor.get_caret_pos()] 524 | new_content = None 525 | 526 | def adjust_caret_pos(m): 527 | caret_pos[0] -= len(m.group(0)) 528 | return '' 529 | 530 | def remove_comment(text): 531 | """ 532 | Remove comment markers from string 533 | @param {Sting} str 534 | @return {String} 535 | """ 536 | text = re.sub(r'^' + re.escape(comment_start) + r'\s*', adjust_caret_pos, text) 537 | return re.sub(r'\s*' + re.escape(comment_end) + '$', '', text) 538 | 539 | def has_match(tx, start): 540 | return content[start:start + len(tx)] == tx 541 | 542 | # first, we need to make sure that this substring is not inside comment 543 | comment_range = search_comment(content, caret_pos[0], comment_start, comment_end) 544 | 545 | if comment_range and comment_range[0] <= range_start and comment_range[1] >= range_end: 546 | # we're inside comment, remove it 547 | range_start, range_end = comment_range 548 | new_content = remove_comment(content[range_start:range_end]) 549 | else: 550 | # should add comment 551 | # make sure that there's no comment inside selection 552 | new_content = '%s %s %s' % (comment_start, re.sub(re.escape(comment_start) + r'\s*|\s*' + re.escape(comment_end), '', content[range_start:range_end]), comment_end) 553 | 554 | # adjust caret position 555 | caret_pos[0] += len(comment_start) + 1 556 | 557 | # replace editor content 558 | if new_content is not None: 559 | d = caret_pos[0] - range_start 560 | new_content = new_content[0:d] + zencoding.utils.get_caret_placeholder() + new_content[d:] 561 | editor.replace_content(zencoding.utils.unindent(editor, new_content), range_start, range_end) 562 | return True 563 | 564 | return False 565 | 566 | @zencoding.action 567 | def split_join_tag(editor, profile_name=None): 568 | """ 569 | Splits or joins tag, e.g. transforms it into a short notation and vice versa: 570 |
: join 571 |
: split 572 | @param editor: Editor instance 573 | @type editor: ZenEditor 574 | @param profile_name: Profile name 575 | @type profile_name: str 576 | """ 577 | caret_pos = editor.get_caret_pos() 578 | profile = zencoding.utils.get_profile(profile_name or editor.get_profile_name()) 579 | caret = zencoding.utils.get_caret_placeholder() 580 | 581 | # find tag at current position 582 | pair = html_matcher.get_tags(editor.get_content(), caret_pos, profile_name or editor.get_profile_name()) 583 | if pair and pair[0]: 584 | new_content = pair[0].full_tag 585 | 586 | if pair[1]: # join tag 587 | closing_slash = '' 588 | if profile['self_closing_tag'] is True: 589 | closing_slash = '/' 590 | elif profile['self_closing_tag'] == 'xhtml': 591 | closing_slash = ' /' 592 | 593 | new_content = re.sub(r'\s*>$', closing_slash + '>', new_content) 594 | 595 | # add caret placeholder 596 | if len(new_content) + pair[0].start < caret_pos: 597 | new_content += caret 598 | else: 599 | d = caret_pos - pair[0].start 600 | new_content = new_content[0:d] + caret + new_content[d:] 601 | 602 | editor.replace_content(new_content, pair[0].start, pair[1].end) 603 | else: # split tag 604 | nl = zencoding.utils.get_newline() 605 | pad = zencoding.utils.get_variable('indentation') 606 | 607 | # define tag content depending on profile 608 | tag_content = profile['tag_nl'] is True and nl + pad + caret + nl or caret 609 | 610 | new_content = '%s%s' % (re.sub(r'\s*\/>$', '>', new_content), tag_content, pair[0].name) 611 | editor.replace_content(new_content, pair[0].start, pair[0].end) 612 | 613 | return True 614 | else: 615 | return False 616 | 617 | 618 | def get_line_bounds(text, pos): 619 | """ 620 | Returns line bounds for specific character position 621 | @type text: str 622 | @param pos: Where to start searching 623 | @type pos: int 624 | @return: list 625 | """ 626 | start = 0 627 | end = len(text) - 1 628 | 629 | # search left 630 | for i in range(pos - 1, 0, -1): 631 | if text[i] in '\n\r': 632 | start = i + 1 633 | break 634 | 635 | # search right 636 | for i in range(pos, len(text)): 637 | if text[i] in '\n\r': 638 | end = i 639 | break 640 | 641 | return start, end 642 | 643 | @zencoding.action 644 | def remove_tag(editor): 645 | """ 646 | Gracefully removes tag under cursor 647 | @type editor: ZenEditor 648 | """ 649 | caret_pos = editor.get_caret_pos() 650 | content = editor.get_content() 651 | 652 | # search for tag 653 | pair = zencoding.html_matcher.get_tags(content, caret_pos, editor.get_profile_name()) 654 | if pair and pair[0]: 655 | if not pair[1]: 656 | # simply remove unary tag 657 | editor.replace_content(zencoding.utils.get_caret_placeholder(), pair[0].start, pair[0].end) 658 | else: 659 | tag_content_range = narrow_to_non_space(content, pair[0].end, pair[1].start) 660 | start_line_bounds = get_line_bounds(content, tag_content_range[0]) 661 | start_line_pad = zencoding.utils.get_line_padding(content[start_line_bounds[0]:start_line_bounds[1]]) 662 | tag_content = content[tag_content_range[0]:tag_content_range[1]] 663 | 664 | tag_content = zencoding.utils.unindent_text(tag_content, start_line_pad) 665 | editor.replace_content(zencoding.utils.get_caret_placeholder() + tag_content, pair[0].start, pair[1].end) 666 | 667 | return True 668 | else: 669 | return False 670 | 671 | def starts_with(token, text, pos=0): 672 | """ 673 | Test if text starts with token at pos 674 | position. If pos is ommited, search from beginning of text 675 | 676 | @param token: Token to test 677 | @type token: str 678 | @param text: Where to search 679 | @type text: str 680 | @param pos: Position where to start search 681 | @type pos: int 682 | @return: bool 683 | @since 0.65 684 | """ 685 | return text[pos] == token[0] and text[pos:pos + len(token)] == token 686 | 687 | @zencoding.action 688 | def encode_decode_base64(editor): 689 | """ 690 | Encodes/decodes image under cursor to/from base64 691 | @type editor: ZenEditor 692 | @since: 0.65 693 | """ 694 | data = editor.get_selection() 695 | caret_pos = editor.get_caret_pos() 696 | 697 | if not data: 698 | # no selection, try to find image bounds from current caret position 699 | text = editor.get_content() 700 | 701 | while caret_pos >= 0: 702 | text[caret_pos:] 703 | 704 | if starts_with('src=', text, caret_pos): # found 705 | m = re.match(r'^(src=(["\'])?)([^\'"<>\s]+)\1?', text[caret_pos:]) 706 | if m: 707 | data = m.group(3) 708 | caret_pos += len(m.group(1)) 709 | break 710 | elif starts_with('url(', text, caret_pos): # found CSS url() pattern 711 | m = re.match(r'^(url\(([\'"])?)([^\'"\)\s]+)\1?', text[caret_pos:]) 712 | if m: 713 | data = m.group(3) 714 | caret_pos += len(m.group(1)) 715 | break 716 | 717 | caret_pos -= 1 718 | 719 | if data: 720 | if starts_with('data:', data): 721 | return decode_from_base64(editor, data, caret_pos) 722 | else: 723 | return encode_to_base64(editor, data, caret_pos) 724 | else: 725 | return False 726 | 727 | def encode_to_base64(editor, img_path, pos): 728 | """ 729 | Encodes image to base64 730 | @requires: zen_file 731 | 732 | @type editor: ZenEditor 733 | @param img_path: Path to image 734 | @type img_path: str 735 | @param pos: Caret position where image is located in the editor 736 | @type pos: int 737 | @return: bool 738 | """ 739 | editor_file = editor.get_file_path() 740 | default_mime_type = 'application/octet-stream' 741 | 742 | if editor_file is None: 743 | raise zencoding.utils.ZenError("You should save your file before using this action") 744 | 745 | 746 | # locate real image path 747 | real_img_path = zen_file.locate_file(editor_file, img_path) 748 | if real_img_path is None: 749 | raise zencoding.utils.ZenError("Can't find '%s' file" % img_path) 750 | 751 | b64 = base64.b64encode(zen_file.read(real_img_path)) 752 | if not b64: 753 | raise zencoding.utils.ZenError("Can't encode file content to base64") 754 | 755 | 756 | b64 = 'data:' + (mime_types[zen_file.get_ext(real_img_path)] or default_mime_type) + ';base64,' + b64 757 | 758 | editor.replace_content('$0' + b64, pos, pos + len(img_path)) 759 | return True 760 | 761 | def decode_from_base64(editor, data, pos): 762 | """ 763 | Decodes base64 string back to file. 764 | @requires: zen_editor.prompt 765 | @requires: zen_file 766 | 767 | @type editor: ZenEditor 768 | @param data: Base64-encoded file content 769 | @type data: str 770 | @param pos: Caret position where image is located in the editor 771 | @type pos: int 772 | """ 773 | # ask user to enter path to file 774 | file_path = editor.prompt('Enter path to file (absolute or relative)') 775 | if not file_path: 776 | return False 777 | 778 | abs_path = zen_file.create_path(editor.get_file_path(), file_path) 779 | if not abs_path: 780 | raise zencoding.utils.ZenError("Can't save file") 781 | 782 | 783 | zen_file.save(abs_path, base64.b64decode( re.sub(r'^data\:.+?;.+?,', '', data) )) 784 | editor.replace_content('$0' + file_path, pos, pos + len(data)) 785 | return True 786 | 787 | def find_expression_bounds(editor, fn): 788 | """ 789 | Find expression bounds in current editor at caret position. 790 | On each character a fn function will be caller which must 791 | return true if current character meets requirements, 792 | false otherwise 793 | @type editor: ZenEditor 794 | @param fn: Function to test each character of expression 795 | @type fn: function 796 | @return: If expression found, returns array with start and end 797 | positions 798 | """ 799 | content = editor.get_content() 800 | il = len(content) 801 | expr_start = editor.get_caret_pos() - 1 802 | expr_end = expr_start + 1 803 | 804 | # start by searching left 805 | while expr_start >= 0 and fn(content[expr_start], expr_start, content): expr_start -= 1 806 | 807 | # then search right 808 | while expr_end < il and fn(content[expr_end], expr_end, content): expr_end += 1 809 | 810 | return expr_end > expr_start and (expr_start + 1, expr_end) or None 811 | 812 | @zencoding.action 813 | def increment_number(editor, step): 814 | """ 815 | Extract number from current caret position of the editor and 816 | increment it by step 817 | @type editor: ZenCoding 818 | @param step: Increment step (may be negative) 819 | @type step: int 820 | """ 821 | content = editor.get_content() 822 | has_sign = [False] 823 | has_decimal = [False] 824 | 825 | def _bounds(ch, start, content): 826 | if ch.isdigit(): 827 | return True 828 | if ch == '.': 829 | if has_decimal[0]: 830 | return False 831 | else: 832 | has_decimal[0] = True 833 | return True 834 | if ch == '-': 835 | if has_sign[0]: 836 | return False 837 | else: 838 | has_sign[0] = True 839 | return True 840 | 841 | return False 842 | 843 | r = find_expression_bounds(editor, _bounds) 844 | if r: 845 | try: 846 | num = content[r[0]:r[1]] 847 | num = zencoding.utils.prettify_number(float(num) + float(step)) 848 | # mark result as selection 849 | editor.replace_content('${0:%s}' % num, r[0], r[1]); 850 | # editor.create_selection(r[0], r[0] + len(num)) 851 | return True 852 | except: 853 | pass 854 | 855 | return False 856 | 857 | @zencoding.action 858 | def increment_number_by_1(editor): 859 | return increment_number(editor, 1) 860 | 861 | @zencoding.action 862 | def decrement_number_by_1(editor): 863 | return increment_number(editor, -1) 864 | 865 | @zencoding.action 866 | def increment_number_by_10(editor): 867 | return increment_number(editor, 10) 868 | 869 | @zencoding.action 870 | def decrement_number_by_10(editor): 871 | return increment_number(editor, -10) 872 | 873 | @zencoding.action 874 | def increment_number_by_01(editor): 875 | return increment_number(editor, 0.1) 876 | 877 | @zencoding.action 878 | def decrement_number_by_01(editor): 879 | return increment_number(editor, -0.1) 880 | 881 | @zencoding.action 882 | def evaluate_math_expression(editor): 883 | """ 884 | Evaluates simple math expresison under caret 885 | @param editor: ZenEditor 886 | """ 887 | content = editor.get_content() 888 | chars = '.+-*/\\' 889 | 890 | r = find_expression_bounds(editor, lambda ch, start, content: ch.isdigit() or ch in chars) 891 | 892 | 893 | if r: 894 | # replace integral division: 11\2 => Math.round(11/2) 895 | expr = re.sub(r'([\d\.\-]+)\\([\d\.\-]+)', 'round($1/$2)', content[r[0]:r[1]]) 896 | 897 | try: 898 | result = zencoding.utils.prettify_number(eval(expr)) 899 | editor.replace_content(result, r[0], r[1]) 900 | editor.set_caret_pos(r[0] + len(result)) 901 | return True 902 | except: 903 | pass 904 | 905 | return False 906 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/actions/token.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Created on Jan 21, 2011 6 | 7 | @author: sergey 8 | ''' 9 | from zencoding.actions.basic import starts_with 10 | from zencoding.utils import prettify_number 11 | import base64 12 | import math 13 | import re 14 | import zencoding 15 | import zencoding.interface.file as zen_file 16 | import zencoding.parser.utils as parser_utils 17 | 18 | @zencoding.action 19 | def reflect_css_value(editor): 20 | """ 21 | Reflect CSS value: takes rule's value under caret and pastes it for the same 22 | rules with vendor prefixes 23 | @param editor: ZenEditor 24 | """ 25 | if editor.get_syntax() != 'css': 26 | return False 27 | 28 | return compound_update(editor, do_css_reflection(editor)) 29 | 30 | @zencoding.action 31 | def update_image_size(editor): 32 | """ 33 | Update image size: reads image from image/CSS rule under caret 34 | and updates dimensions inside tag/rule 35 | @type editor: ZenEditor 36 | """ 37 | if editor.get_syntax() == 'css': 38 | result = update_image_size_css(editor) 39 | else: 40 | result = update_image_size_html(editor) 41 | 42 | return compound_update(editor, result) 43 | 44 | def compound_update(editor, data): 45 | if data: 46 | text = data['data'] 47 | 48 | sel_start, sel_end = editor.get_selection_range() 49 | 50 | # try to preserve caret position 51 | if data['caret'] < data['start'] + len(text): 52 | relative_pos = data['caret'] - data['start'] 53 | if relative_pos >= 0: 54 | text = text[:relative_pos] + zencoding.utils.get_caret_placeholder() + text[relative_pos:] 55 | 56 | editor.replace_content(text, data['start'], data['end'], True) 57 | # editor.replace_content(zencoding.utils.unindent(editor, text), data['start'], data['end']) 58 | editor.create_selection(data['caret'], data['caret'] + sel_end - sel_start) 59 | return True 60 | 61 | return False 62 | 63 | def update_image_size_html(editor): 64 | """ 65 | Updates image size of <img src=""> tag 66 | @type editor: ZenEditor 67 | """ 68 | editor_file = editor.get_file_path() 69 | caret_pos = editor.get_caret_pos() 70 | 71 | if editor_file is None: 72 | raise zencoding.utils.ZenError("You should save your file before using this action") 73 | 74 | image = _find_image(editor) 75 | 76 | if image: 77 | # search for image path 78 | m = re.search(r'src=(["\'])(.+?)\1', image['tag'], re.IGNORECASE) 79 | if m: 80 | src = m.group(2) 81 | 82 | if src: 83 | size = get_image_size_for_source(editor, src) 84 | if size: 85 | new_tag = _replace_or_append(image['tag'], 'width', size['width']) 86 | new_tag = _replace_or_append(new_tag, 'height', size['height']) 87 | 88 | return { 89 | 'data': new_tag, 90 | 'start': image['start'], 91 | 'end': image['end'], 92 | 'caret': caret_pos 93 | } 94 | return False 95 | 96 | def get_image_size_for_source(editor, src): 97 | """ 98 | Returns image dimentions for source 99 | @param {zen_editor} editor 100 | @param {String} src Image source (path or data:url) 101 | """ 102 | if src: 103 | # check if it is data:url 104 | if starts_with('data:', src): 105 | f_content = base64.b64decode( re.sub(r'^data\:.+?;.+?,', '', src) ) 106 | else: 107 | editor_file = editor.get_file_path() 108 | 109 | if editor_file is None: 110 | raise zencoding.utils.ZenError("You should save your file before using this action") 111 | 112 | abs_src = zen_file.locate_file(editor_file, src) 113 | if not abs_src: 114 | raise zencoding.utils.ZenError("Can't locate '%s' file" % src) 115 | 116 | f_content = zen_file.read(abs_src) 117 | 118 | return zencoding.utils.get_image_size(f_content) 119 | 120 | def _replace_or_append(img_tag, attr_name, attr_value): 121 | """ 122 | Replaces or adds attribute to the tag 123 | @type img_tag: str 124 | @type attr_name: str 125 | @type attr_value: str 126 | """ 127 | if attr_name in img_tag.lower(): 128 | # attribute exists 129 | re_attr = re.compile(attr_name + r'=([\'"])(.*?)\1', re.I) 130 | return re.sub(re_attr, lambda m: '%s=%s%s%s' % (attr_name, m.group(1), attr_value, m.group(1)), img_tag) 131 | else: 132 | return re.sub(r'\s*(\/?>)$', ' %s="%s" \\1' % (attr_name, attr_value), img_tag) 133 | 134 | def _find_image(editor): 135 | """ 136 | Find image tag under caret 137 | @return Image tag and its indexes inside editor source 138 | """ 139 | _caret = editor.get_caret_pos() 140 | text = editor.get_content() 141 | start_ix = -1 142 | end_ix = -1 143 | 144 | # find the beginning of the tag 145 | caret_pos = _caret 146 | while caret_pos >= 0: 147 | if text[caret_pos] == '<': 148 | if text[caret_pos:caret_pos + 4].lower() == '': 162 | end_ix = caret_pos + 1 163 | break 164 | caret_pos += 1 165 | 166 | 167 | if start_ix != -1 and end_ix != -1: 168 | return { 169 | 'start': start_ix, 170 | 'end': end_ix, 171 | 'tag': text[start_ix:end_ix] 172 | } 173 | 174 | return None 175 | 176 | def find_css_insertion_point(tokens, start_ix): 177 | """ 178 | Search for insertion point for new CSS properties 179 | @param tokens: List of parsed CSS tokens 180 | @param start_ix: Token index where to start searching 181 | """ 182 | ins_point = None 183 | ins_ix = -1 184 | need_col = False 185 | 186 | for i in range(start_ix, len(tokens)): 187 | t = tokens[i] 188 | if t['type'] == 'value': 189 | ins_point = t 190 | ins_ix = i 191 | 192 | # look ahead for rule termination 193 | if i + 1 < len(tokens) and tokens[i + 1]['type'] == ';': 194 | ins_point = tokens[i + 1] 195 | ins_ix += 1 196 | else: 197 | need_col = True 198 | 199 | break 200 | 201 | return { 202 | 'token': ins_point, 203 | 'ix': ins_ix, 204 | 'need_col': need_col 205 | } 206 | 207 | def update_image_size_css(editor): 208 | """ 209 | Updates image size of CSS rule 210 | @type editor: ZenEditor 211 | """ 212 | caret_pos = editor.get_caret_pos() 213 | content = editor.get_content() 214 | rule = parser_utils.extract_css_rule(content, caret_pos, True) 215 | 216 | if rule: 217 | css = parser_utils.parse_css(content[rule[0]:rule[1]], rule[0]) 218 | cur_token = find_token_from_position(css, caret_pos, 'identifier') 219 | value = find_value_token(css, cur_token + 1) 220 | 221 | if not value: return False 222 | 223 | # find insertion point 224 | ins_point = find_css_insertion_point(css, cur_token) 225 | 226 | m = re.match(r'url\((["\']?)(.+?)\1\)', value['content'], re.I) 227 | if m: 228 | size = get_image_size_for_source(editor, m.group(2)) 229 | if size: 230 | wh = {'width': None, 'height': None} 231 | updates = [] 232 | styler = learn_css_style(css, cur_token) 233 | 234 | for i, item in enumerate(css): 235 | if item['type'] == 'identifier' and item['content'] in wh: 236 | wh[item['content']] = i 237 | 238 | def update(name, val): 239 | v = None 240 | if wh[name] is not None: 241 | v = find_value_token(css, wh[name] + 1) 242 | 243 | if v: 244 | updates.append([v['start'], v['end'], '%spx' % val]) 245 | else: 246 | updates.append([ins_point['token']['end'], ins_point['token']['end'], styler(name, '%spx' % val)]) 247 | 248 | 249 | update('width', size['width']) 250 | update('height', size['height']) 251 | 252 | if updates: 253 | updates.sort(lambda a,b: a[0] - b[0]) 254 | # updates = sorted(updates, key=lambda a: a[0]) 255 | 256 | # some editors do not provide easy way to replace multiple code 257 | # fragments so we have to squash all replace operations into one 258 | offset = updates[0][0] 259 | offset_end = updates[-1][1] 260 | data = content[offset:offset_end] 261 | 262 | updates.reverse() 263 | for u in updates: 264 | data = replace_substring(data, u[0] - offset, u[1] - offset, u[2]) 265 | 266 | # also calculate new caret position 267 | if u[0] < caret_pos: 268 | caret_pos += len(u[2]) - u[1] + u[0] 269 | 270 | 271 | if ins_point['need_col']: 272 | data = replace_substring(data, ins_point['token']['end'] - offset, ins_point['token']['end'] - offset, ';') 273 | 274 | return { 275 | 'data': data, 276 | 'start': offset, 277 | 'end': offset_end, 278 | 'caret': caret_pos 279 | }; 280 | 281 | return None 282 | 283 | def learn_css_style(tokens, pos): 284 | """ 285 | Learns formatting style from parsed tokens 286 | @param tokens: List of tokens 287 | @param pos: Identifier token position, from which style should be learned 288 | @returns: Function with (name, value) arguments that will create 289 | CSS rule based on learned formatting 290 | """ 291 | prefix = '' 292 | glue = '' 293 | 294 | # use original tokens instead of optimized ones 295 | pos = tokens[pos]['ref_start_ix'] 296 | tokens = tokens.original 297 | 298 | # learn prefix 299 | for i in xrange(pos - 1, -1, -1): 300 | if tokens[i]['type'] == 'white': 301 | prefix = tokens[i]['content'] + prefix 302 | elif tokens[i]['type'] == 'line': 303 | prefix = tokens[i]['content'] + prefix 304 | break 305 | else: 306 | break 307 | 308 | # learn glue 309 | for t in tokens[pos+1:]: 310 | if t['type'] == 'white' or t['type'] == ':': 311 | glue += t['content'] 312 | else: 313 | break 314 | 315 | if ':' not in glue: 316 | glue = ':' 317 | 318 | return lambda name, value: "%s%s%s%s;" % (prefix, name, glue, value) 319 | 320 | 321 | def do_css_reflection(editor): 322 | content = editor.get_content() 323 | caret_pos = editor.get_caret_pos() 324 | css = parser_utils.extract_css_rule(content, caret_pos) 325 | 326 | if not css or caret_pos < css[0] or caret_pos > css[1]: 327 | # no matching CSS rule or caret outside rule bounds 328 | return False 329 | 330 | tokens = parser_utils.parse_css(content[css[0]:css[1]], css[0]) 331 | token_ix = find_token_from_position(tokens, caret_pos, 'identifier') 332 | 333 | if token_ix != -1: 334 | cur_prop = tokens[token_ix]['content'] 335 | value_token = find_value_token(tokens, token_ix + 1) 336 | base_name = get_base_css_name(cur_prop) 337 | re_name = re.compile('^(?:\\-\\w+\\-)?' + base_name + '$') 338 | re_name = get_reflected_css_name(base_name) 339 | values = [] 340 | 341 | if not value_token: 342 | return False 343 | 344 | # search for all vendor-prefixed properties 345 | for i, token in enumerate(tokens): 346 | if token['type'] == 'identifier' and re.search(re_name, token['content']) and token['content'] != cur_prop: 347 | v = find_value_token(tokens, i + 1) 348 | if v: 349 | values.append({'name': token, 'value': v}) 350 | 351 | # some editors do not provide easy way to replace multiple code 352 | # fragments so we have to squash all replace operations into one 353 | if values: 354 | data = content[values[0]['value']['start']:values[-1]['value']['end']] 355 | offset = values[0]['value']['start'] 356 | value = value_token['content'] 357 | 358 | values.reverse() 359 | for v in values: 360 | rv = get_reflected_value(cur_prop, value, v['name']['content'], v['value']['content']) 361 | data = replace_substring(data, v['value']['start'] - offset, v['value']['end'] - offset, rv) 362 | 363 | # also calculate new caret position 364 | if v['value']['start'] < caret_pos: 365 | caret_pos += len(rv) - len(v['value']['content']) 366 | 367 | return { 368 | 'data': data, 369 | 'start': offset, 370 | 'end': values[0]['value']['end'], 371 | 'caret': caret_pos 372 | } 373 | 374 | return None 375 | 376 | def get_base_css_name(name): 377 | """ 378 | Removes vendor prefix from CSS property 379 | @param name: CSS property 380 | @type name: str 381 | @return: str 382 | """ 383 | return re.sub(r'^\s*\-\w+\-', '', name) 384 | 385 | def get_reflected_css_name(name): 386 | """ 387 | Returns regexp that should match reflected CSS property names 388 | @param name: Current CSS property name 389 | @type name: str 390 | @return: RegExp 391 | """ 392 | name = get_base_css_name(name) 393 | vendor_prefix = '^(?:\\-\\w+\\-)?' 394 | 395 | if name == 'opacity' or name == 'filter': 396 | return re.compile(vendor_prefix + '(?:opacity|filter)$') 397 | 398 | m = re.match(r'^border-radius-(top|bottom)(left|right)', name) 399 | if m: 400 | # Mozilla-style border radius 401 | return re.compile(vendor_prefix + '(?:%s|border-%s-%s-radius)$' % (name, m.group(1), m.group(2)) ) 402 | 403 | m = re.match(r'^border-(top|bottom)-(left|right)-radius', name) 404 | if m: 405 | return re.compile(vendor_prefix + '(?:%s|border-radius-%s%s)$' % (name, m.group(1), m.group(2)) ); 406 | 407 | return re.compile(vendor_prefix + name + '$') 408 | 409 | def get_reflected_value(cur_name, cur_value, ref_name, ref_value): 410 | """ 411 | Returns value that should be reflected for ref_name CSS property 412 | from cur_name property. This function is used for special cases, 413 | when the same result must be achieved with different properties for different 414 | browsers. For example: opаcity:0.5; -> filter:alpha(opacity=50);

415 | 416 | This function does value conversion between different CSS properties 417 | 418 | @param cur_name: Current CSS property name 419 | @type cur_name: str 420 | @param cur_value: Current CSS property value 421 | @type cur_value: str 422 | @param ref_name: Receiver CSS property's name 423 | @type ref_name: str 424 | @param ref_value: Receiver CSS property's value 425 | @type ref_value: str 426 | @return: New value for receiver property 427 | """ 428 | cur_name = get_base_css_name(cur_name) 429 | ref_name = get_base_css_name(ref_name) 430 | 431 | if cur_name == 'opacity' and ref_name == 'filter': 432 | return re.sub(re.compile(r'opacity=[^\)]*', re.IGNORECASE), 'opacity=' + math.floor(float(cur_value) * 100), ref_value) 433 | if cur_name == 'filter' and ref_name == 'opacity': 434 | m = re.search(r'opacity=([^\)]*)', cur_value, re.IGNORECASE) 435 | return prettify_number(int(m.group(1)) / 100) if m else ref_value 436 | 437 | 438 | return cur_value 439 | 440 | def find_value_token(tokens, pos): 441 | """ 442 | Find value token, staring at pos index and moving right 443 | @type tokens: list 444 | @type pos: int 445 | @return: token 446 | """ 447 | for t in tokens[pos:]: 448 | if t['type'] == 'value': 449 | return t 450 | elif t['type'] == 'identifier' or t['type'] == ';': 451 | break 452 | 453 | return None 454 | 455 | def replace_substring(text, start, end, new_value): 456 | """ 457 | Replace substring of text, defined by start and 458 | end indexes with new_value 459 | @type text: str 460 | @type start: int 461 | @type end: int 462 | @type new_value: str 463 | @return: str 464 | """ 465 | return text[0:start] + new_value + text[end:] 466 | 467 | def find_token_from_position(tokens, pos, type): 468 | """ 469 | Search for token with specified type left to the specified position 470 | @param tokens: List of parsed tokens 471 | @type tokens: list 472 | @param pos: Position where to start searching 473 | @type pos: int 474 | @param type: Token type 475 | @type type: str 476 | @return: Token index 477 | """ 478 | # find token under caret 479 | token_ix = -1; 480 | for i, token in enumerate(tokens): 481 | if token['start'] <= pos and token['end'] >= pos: 482 | token_ix = i 483 | break 484 | 485 | if token_ix != -1: 486 | # token found, search left until we find token with specified type 487 | while token_ix >= 0: 488 | if tokens[token_ix]['type'] == type: 489 | return token_ix 490 | token_ix -= 1 491 | 492 | return -1 493 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/actions/traverse.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Actions that use stream parsers and tokenizers for traversing: 3 | -- Search for next/previous items in HTML 4 | -- Search for next/previous items in CSS 5 | 6 | Created on Jan 21, 2011 7 | 8 | @author: sergey 9 | ''' 10 | import re 11 | import zencoding 12 | import zencoding.parser.utils as parser_utils 13 | 14 | start_tag = re.compile('^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*)\s*(\/?)>') 15 | known_xml_types = ['xml-tagname','xml-attname', 'xml-attribute'] 16 | known_css_types = ['selector', 'identifier', 'value'] 17 | 18 | def find_next_html_item(editor): 19 | """ 20 | Find next HTML item 21 | @param editor: ZenEditor 22 | """ 23 | is_first = [True] 24 | 25 | def fn(content, search_pos, is_backward=False): 26 | if is_first[0]: 27 | is_first[0] = False 28 | return find_opening_tag_from_position(content, search_pos) 29 | else: 30 | return get_opening_tag_from_position(content, search_pos) 31 | 32 | return find_item(editor, False, fn, get_range_for_next_item_in_html) 33 | 34 | def find_prev_html_item(editor): 35 | """ 36 | Find previous HTML item 37 | @param editor: ZenEditor 38 | """ 39 | return find_item(editor, True, get_opening_tag_from_position, get_range_for_prev_item_in_html) 40 | 41 | def get_range_for_next_item_in_html(tag, offset, sel_start, sel_end): 42 | """ 43 | Returns range for item to be selected in tag after current caret position 44 | @param tag: Tag declaration 45 | @type tag: str 46 | 47 | @param offset: Tag's position index inside content 48 | @type offset: int 49 | 50 | @param sel_start: Start index of user selection 51 | @type sel_start: int 52 | 53 | @param sel_end: End index of user selection 54 | @type sel_end: int 55 | 56 | @return List with two indexes if next item was found, None otherwise 57 | """ 58 | tokens = parser_utils.parse_html(tag, offset) 59 | next = [] 60 | 61 | # search for token that is right to selection 62 | for i, token in enumerate(tokens): 63 | if token['type'] in known_xml_types: 64 | # check token position 65 | pos_test = token['start'] >= sel_start 66 | if token['type'] == 'xml-attribute' and is_quote(token['content'][0]): 67 | pos_test = token['start'] + 1 >= sel_start and token['end'] -1 != sel_end 68 | 69 | if not pos_test and not (sel_start == sel_end and token['end'] > sel_start): 70 | continue 71 | 72 | # found token that should be selected 73 | if token['type'] == 'xml-attname': 74 | next = handle_full_attribute_html(tokens, i, sel_end <= token['end'] and token['start'] or -1) 75 | if next: 76 | return next 77 | elif token['end'] > sel_end: 78 | next = [token['start'], token['end']] 79 | 80 | if token['type'] == 'xml-attribute': 81 | next = handle_quotes_html(token['content'], next) 82 | 83 | if sel_start == next[0] and sel_end == next[1]: 84 | # in case of empty attribute 85 | continue 86 | 87 | return next 88 | 89 | return None 90 | 91 | def get_range_for_prev_item_in_html(tag, offset, sel_start, sel_end): 92 | """ 93 | Returns range for item to be selected in tag before current caret position 94 | @param tag: Tag declaration 95 | @type tag: str 96 | 97 | @param offset: Tag's position index inside content 98 | @type offset: int 99 | 100 | @param sel_start: Start index of user selection 101 | @type sel_start: int 102 | 103 | @param sel_end: End index of user selection 104 | @type sel_end: int 105 | 106 | @return List with two indexes if next item was found, None otherwise 107 | """ 108 | tokens = parser_utils.parse_html(tag, offset) 109 | 110 | # search for token that is left to the selection 111 | for i in range(len(tokens) - 1, -1, -1): 112 | token = tokens[i] 113 | if token['type'] in known_xml_types: 114 | # check token position 115 | pos_test = token['start'] < sel_start 116 | if token['type'] == 'xml-attribute' and is_quote(token['content'][0]): 117 | pos_test = token['start'] + 1 < sel_start 118 | 119 | if not pos_test: continue 120 | 121 | # found token that should be selected 122 | if token['type'] == 'xml-attname': 123 | next = handle_full_attribute_html(tokens, i, token['start']) 124 | if next: return next 125 | else: 126 | next = [token['start'], token['end']] 127 | 128 | if token['type'] == 'xml-attribute': 129 | next = handle_quotes_html(token['content'], next) 130 | 131 | return next 132 | 133 | return None 134 | 135 | def find_opening_tag_from_position(html, pos): 136 | """ 137 | Search for opening tag in content, starting at specified position 138 | @param html: Where to search tag 139 | @type html: str 140 | 141 | @param pos: Character index where to start searching 142 | @type pos: int 143 | 144 | @return: List with tag indexes if valid opening tag was found, None otherwise 145 | """ 146 | while pos >= 0: 147 | tag = get_opening_tag_from_position(html, pos) 148 | if tag: 149 | return tag 150 | pos -= 1 151 | 152 | return None 153 | 154 | def get_opening_tag_from_position(html, pos, is_backward=False): 155 | """ 156 | @param html: Where to search tag 157 | @type html: str 158 | 159 | @param pos: Character index where to start searching 160 | @type pos: int 161 | 162 | @return: List with tag indexes if valid opening tag was found, None otherwise 163 | """ 164 | if html[pos] == '<': 165 | m = re.search(start_tag, html[pos:]) 166 | if m: 167 | return [pos, pos + len(m.group(0))] 168 | 169 | return None 170 | 171 | 172 | def is_quote(ch): 173 | return ch == '"' or ch == "'" 174 | 175 | def find_item(editor, is_backward, extract_fn, range_fn): 176 | """ 177 | @type editor: ZenEditor 178 | 179 | @param is_backward: Search backward (search forward otherwise) 180 | @type is_backward: boolean 181 | 182 | @param extract_fn: Function that extracts item content 183 | @type extract_fn: function 184 | 185 | @param range_rn: Function that search for next token range 186 | @type range_rn: function 187 | """ 188 | content = editor.get_content() 189 | c_len = len(content) 190 | loop = 100000 # endless loop protection 191 | prev_range = [-1, -1] 192 | _sel_start, _sel_end = editor.get_selection_range() 193 | sel_start = min(_sel_start, _sel_end) 194 | sel_end = max(_sel_start, _sel_end) 195 | 196 | search_pos = sel_start 197 | while search_pos >= 0 and search_pos < c_len and loop > 0: 198 | loop -= 1 199 | item = extract_fn(content, search_pos, is_backward) 200 | if item: 201 | if prev_range[0] == item[0] and prev_range[1] == item[1]: 202 | break 203 | 204 | prev_range[0] = item[0] 205 | prev_range[1] = item[1] 206 | item_def = content[item[0]:item[1]] 207 | rng = range_fn(item_def, item[0], sel_start, sel_end) 208 | 209 | if rng: 210 | editor.create_selection(rng[0], rng[1]) 211 | return True 212 | else: 213 | search_pos = is_backward and item[0] or item[1] - 1 214 | 215 | search_pos += is_backward and -1 or 1 216 | 217 | return False 218 | 219 | def find_next_css_item(editor): 220 | return find_item(editor, False, parser_utils.extract_css_rule, get_range_for_next_item_in_css) 221 | 222 | def find_prev_css_item(editor): 223 | return find_item(editor, True, parser_utils.extract_css_rule, get_range_for_prev_item_in_css) 224 | 225 | 226 | def get_range_for_next_item_in_css(rule, offset, sel_start, sel_end): 227 | """ 228 | Returns range for item to be selected in tag after current caret position 229 | 230 | @param rule: CSS rule declaration 231 | @type rule: str 232 | 233 | @param offset: Rule's position index inside content 234 | @type offset: int 235 | 236 | @param sel_start: Start index of user selection 237 | @type sel_start: int 238 | 239 | @param sel_end: End index of user selection 240 | @type sel_end: int 241 | 242 | @return: List with two indexes if next item was found, None otherwise 243 | """ 244 | tokens = parser_utils.parse_css(rule, offset) 245 | next = [] 246 | 247 | def check_same_range(r): 248 | "Same range is used inside complex value processor" 249 | return r[0] == sel_start and r[1] == sel_end 250 | 251 | 252 | # search for token that is right to selection 253 | for i, token in enumerate(tokens): 254 | if token['type'] in known_css_types: 255 | # check token position 256 | if sel_start == sel_end: 257 | pos_test = token['end'] > sel_start 258 | else: 259 | pos_test = token['start'] >= sel_start 260 | if token['type'] == 'value': # respect complex values 261 | pos_test = pos_test or sel_start >= token['start'] and token['end'] >= sel_end 262 | 263 | if not pos_test: continue 264 | 265 | # found token that should be selected 266 | if token['type'] == 'identifier': 267 | rule_sel = handle_full_rule_css(tokens, i, sel_end <= token['end'] and token['start'] or -1) 268 | if rule_sel: 269 | return rule_sel 270 | 271 | elif token['type'] == 'value' and sel_end > token['start'] and token['children']: 272 | # looks like a complex value 273 | children = token['children'] 274 | for child in children: 275 | if child[0] >= sel_start or (sel_start == sel_end and child[1] > sel_start): 276 | next = [child[0], child[1]] 277 | if check_same_range(next): 278 | rule_sel = handle_css_special_case(rule, next[0], next[1], offset) 279 | if not check_same_range(rule_sel): 280 | return rule_sel 281 | else: 282 | continue 283 | 284 | return next 285 | elif token['end'] > sel_end: 286 | return [token['start'], token['end']] 287 | 288 | return None 289 | 290 | def get_range_for_prev_item_in_css(rule, offset, sel_start, sel_end): 291 | """ 292 | Returns range for item to be selected in CSS rule before current caret position 293 | 294 | @param rule: CSS rule declaration 295 | @type rule: str 296 | 297 | @param offset: Rule's position index inside content 298 | @type offset: int 299 | 300 | @param sel_start: Start index of user selection 301 | @type sel_start: int 302 | 303 | @param sel_end: End index of user selection 304 | @type sel_end: int 305 | 306 | @return: List with two indexes if next item was found, None otherwise 307 | """ 308 | tokens = parser_utils.parse_css(rule, offset) 309 | next = [] 310 | 311 | def check_same_range(r): 312 | "Same range is used inside complex value processor" 313 | return r[0] == sel_start and r[1] == sel_end 314 | 315 | # search for token that is left to the selection 316 | for i in range(len(tokens) - 1, -1, -1): 317 | token = tokens[i] 318 | if token['type'] in known_css_types: 319 | # check token position 320 | pos_test = token['start'] < sel_start 321 | if token['type'] == 'value' and token['ref_start_ix'] != token['ref_end_ix']: # respect complex values 322 | pos_test = token['start'] <= sel_start 323 | 324 | if not pos_test: continue 325 | 326 | # found token that should be selected 327 | if token['type'] == 'identifier': 328 | rule_sel = handle_full_rule_css(tokens, i, token['start']) 329 | if rule_sel: 330 | return rule_sel 331 | elif token['type'] == 'value' and token['ref_start_ix'] != token['ref_end_ix']: 332 | # looks like a complex value 333 | children = token['children'] 334 | for child in children: 335 | if child[0] < sel_start: 336 | # create array copy 337 | next = [child[0], child[1]] 338 | 339 | rule_sel = handle_css_special_case(rule, next[0], next[1], offset) 340 | return not check_same_range(rule_sel) and rule_sel or next 341 | 342 | # if we are here than we already traversed trough all 343 | # child tokens, select full value 344 | next = [token['start'], token['end']] 345 | if not check_same_range(next): 346 | return next 347 | else: 348 | return [token['start'], token['end']] 349 | 350 | return None 351 | 352 | def handle_full_rule_css(tokens, i, start): 353 | for t in tokens[i+1:]: 354 | if (t['type'] == 'value' and start == -1) or t['type'] == 'identifier': 355 | return [t['start'], t['end']] 356 | elif t['type'] == ';': 357 | return [start == -1 and t['start'] or start, t['end']] 358 | elif t['type'] == '}': 359 | return [start == -1 and t['start'] or start, t['start'] - 1] 360 | 361 | return None 362 | 363 | def handle_full_attribute_html(tokens, i, start): 364 | for t in tokens[i+1:]: 365 | if t['type'] == 'xml-attribute': 366 | if start == -1: 367 | return handle_quotes_html(t['content'], [t['start'], t['end']]) 368 | else: 369 | return [start, t['end']] 370 | elif t['type'] == 'xml-attname': 371 | # moved to next attribute, adjust selection 372 | return [t['start'], tokens[i]['end']] 373 | 374 | return None 375 | 376 | def handle_quotes_html(attr, r): 377 | if is_quote(attr[0]): 378 | r[0] += 1 379 | if is_quote(attr[-1]): 380 | r[1] -= 1 381 | 382 | return r 383 | 384 | def handle_css_special_case(text, start, end, offset): 385 | text = text[start - offset:end - offset] 386 | m = re.match(r'^[\w\-]+\([\'"]?', text) 387 | if m: 388 | start += len(m.group(0)) 389 | m = re.search(r'[\'"]?\)$', text) 390 | if m: 391 | end -= len(m.group(0)) 392 | 393 | return [start, end] 394 | 395 | @zencoding.action 396 | def select_next_item(editor): 397 | if editor.get_syntax() == 'css': 398 | return find_next_css_item(editor) 399 | else: 400 | return find_next_html_item(editor) 401 | 402 | @zencoding.action 403 | def select_previous_item(editor): 404 | if editor.get_syntax() == 'css': 405 | return find_prev_css_item(editor) 406 | else: 407 | return find_prev_html_item(editor) 408 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | import sys 6 | 7 | # import all filters 8 | __sub_modules = [] 9 | 10 | for file in os.listdir(os.path.dirname(__file__)): 11 | name, ext = os.path.splitext(file) 12 | if ext.lower() == '.py': 13 | __sub_modules.append(name) 14 | 15 | __import__(__name__, globals(), locals(), __sub_modules) 16 | 17 | del __sub_modules -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/comment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Comment important tags (with 'id' and 'class' attributes) 6 | @author Sergey Chikuyonok (serge.che@gmail.com) 7 | @link http://chikuyonok.ru 8 | ''' 9 | import zencoding 10 | import zencoding.utils as utils 11 | 12 | def add_comments(node, i): 13 | 14 | """ 15 | Add comments to tag 16 | @type node: ZenNode 17 | @type i: int 18 | """ 19 | id_attr = node.get_attribute('id') 20 | class_attr = node.get_attribute('class') 21 | nl = utils.get_newline() 22 | 23 | if id_attr or class_attr: 24 | comment_str = '' 25 | padding = node.parent and node.parent.padding or '' 26 | if id_attr: comment_str += '#' + id_attr 27 | if class_attr: comment_str += '.' + class_attr 28 | 29 | node.start = node.start.replace('<', '' + nl + padding + '<', 1) 30 | node.end = node.end.replace('>', '>' + nl + padding + '', 1) 31 | 32 | # replace counters 33 | counter = zencoding.utils.get_counter_for_node(node) 34 | node.start = utils.replace_counter(node.start, counter) 35 | node.end = utils.replace_counter(node.end, counter) 36 | 37 | @zencoding.filter('c') 38 | def process(tree, profile): 39 | if profile['tag_nl'] is False: 40 | return tree 41 | 42 | for i, item in enumerate(tree.children): 43 | if item.is_block(): 44 | add_comments(item, i) 45 | process(item, profile) 46 | 47 | return tree -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/css.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | Process CSS properties: replaces snippets, augumented with ! char, with 5 | !important suffix 6 | @author Sergey Chikuyonok (serge.che@gmail.com) 7 | @link http://chikuyonok.ru 8 | ''' 9 | import re 10 | import zencoding 11 | 12 | re_important = re.compile(r'(.+)\!$') 13 | 14 | @zencoding.filter('css') 15 | def process(tree, profile): 16 | for item in tree.children: 17 | # CSS properties are always snippets 18 | if item.type == 'snippet' and re_important.search(item.real_name): 19 | item.start = re.sub(r'(;?)$', ' !important\\1', item.start) 20 | 21 | process(item, profile) 22 | 23 | return tree -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/escape.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Filter for escaping unsafe XML characters: <, >, & 6 | @author Sergey Chikuyonok (serge.che@gmail.com) 7 | @link http://chikuyonok.ru 8 | ''' 9 | import re 10 | import zencoding 11 | 12 | char_map = { 13 | '<': '<', 14 | '>': '>', 15 | '&': '&' 16 | } 17 | 18 | re_chars = re.compile(r'[<>&]') 19 | 20 | def escape_chars(text): 21 | return re_chars.sub(lambda m: char_map[m.group(0)], text) 22 | 23 | @zencoding.filter('e') 24 | def process(tree, profile=None): 25 | for item in tree.children: 26 | item.start = escape_chars(item.start) 27 | item.end = escape_chars(item.end) 28 | 29 | process(item) 30 | 31 | return tree -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/format-css.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Format CSS properties: add space after property name: 6 | padding:0; -> padding: 0; 7 | @author Sergey Chikuyonok (serge.che@gmail.com) 8 | @link http://chikuyonok.ru 9 | ''' 10 | import re 11 | import zencoding 12 | 13 | re_css_prop = re.compile(r'([\w\-]+\s*:)(?!:)\s*') 14 | 15 | @zencoding.filter('fc') 16 | def process(tree, profile): 17 | for item in tree.children: 18 | # CSS properties are always snippets 19 | if item.type == 'snippet': 20 | item.start = re_css_prop.sub(r'\1 ', item.start) 21 | 22 | process(item, profile) 23 | 24 | return tree -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/format.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Generic formatting filter: creates proper indentation for each tree node, 6 | placing "%s" placeholder where the actual output should be. You can use 7 | this filter to preformat tree and then replace %s placeholder to whatever you 8 | need. This filter should't be called directly from editor as a part 9 | of abbreviation. 10 | @author Sergey Chikuyonok (serge.che@gmail.com) 11 | @link http://chikuyonok.ru 12 | """ 13 | import re 14 | import zencoding.utils 15 | 16 | child_token = '${child}' 17 | placeholder = '%s' 18 | 19 | def get_newline(): 20 | return zencoding.utils.get_newline() 21 | 22 | 23 | def get_indentation(): 24 | return zencoding.utils.get_indentation() 25 | 26 | def has_block_sibling(item): 27 | """ 28 | Test if passed node has block-level sibling element 29 | @type item: ZenNode 30 | @return: bool 31 | """ 32 | return item.parent and item.parent.has_block_children() 33 | 34 | def is_very_first_child(item): 35 | """ 36 | Test if passed itrem is very first child of the whole tree 37 | @type tree: ZenNode 38 | """ 39 | return item.parent and not item.parent.parent and not item.previous_sibling 40 | 41 | def should_break_line(node, profile): 42 | """ 43 | Need to add line break before element 44 | @type node: ZenNode 45 | @type profile: dict 46 | @return: bool 47 | """ 48 | if not profile['inline_break']: 49 | return False 50 | 51 | # find toppest non-inline sibling 52 | while node.previous_sibling and node.previous_sibling.is_inline(): 53 | node = node.previous_sibling 54 | 55 | if not node.is_inline(): 56 | return False 57 | 58 | # calculate how many inline siblings we have 59 | node_count = 1 60 | node = node.next_sibling 61 | while node: 62 | if node.is_inline(): 63 | node_count += 1 64 | else: 65 | break 66 | node = node.next_sibling 67 | 68 | return node_count >= profile['inline_break'] 69 | 70 | def should_break_child(node, profile): 71 | """ 72 | Need to add newline because item has too many inline children 73 | @type node: ZenNode 74 | @type profile: dict 75 | @return: bool 76 | """ 77 | # we need to test only one child element, because 78 | # has_block_children() method will do the rest 79 | return node.children and should_break_line(node.children[0], profile) 80 | 81 | def process_snippet(item, profile, level=0): 82 | """ 83 | Processes element with snippet type 84 | @type item: ZenNode 85 | @type profile: dict 86 | @param level: Depth level 87 | @type level: int 88 | """ 89 | data = item.source.value; 90 | 91 | if not data: 92 | # snippet wasn't found, process it as tag 93 | return process_tag(item, profile, level) 94 | 95 | item.start = placeholder 96 | item.end = placeholder 97 | 98 | padding = item.parent.padding if item.parent else get_indentation() * level 99 | 100 | if not is_very_first_child(item): 101 | item.start = get_newline() + padding + item.start 102 | 103 | # adjust item formatting according to last line of start property 104 | parts = data.split(child_token) 105 | lines = zencoding.utils.split_by_lines(parts[0] or '') 106 | padding_delta = get_indentation() 107 | 108 | if len(lines) > 1: 109 | m = re.match(r'^(\s+)', lines[-1]) 110 | if m: 111 | padding_delta = m.group(1) 112 | 113 | item.padding = padding + padding_delta 114 | 115 | return item 116 | 117 | def process_tag(item, profile, level=0): 118 | """ 119 | Processes element with tag type 120 | @type item: ZenNode 121 | @type profile: dict 122 | @param level: Depth level 123 | @type level: int 124 | """ 125 | if not item.name: 126 | # looks like it's a root element 127 | return item 128 | 129 | item.start = placeholder 130 | item.end = placeholder 131 | 132 | is_unary = item.is_unary() and not item.children 133 | 134 | # formatting output 135 | if profile['tag_nl'] is not False: 136 | padding = item.parent.padding if item.parent else get_indentation() * level 137 | force_nl = profile['tag_nl'] is True 138 | should_break = should_break_line(item, profile) 139 | 140 | # formatting block-level elements 141 | if ((item.is_block() or should_break) and item.parent) or force_nl: 142 | # snippet children should take different formatting 143 | if not item.parent or (item.parent.type != 'snippet' and not is_very_first_child(item)): 144 | item.start = get_newline() + padding + item.start 145 | 146 | if item.has_block_children() or should_break_child(item, profile) or (force_nl and not is_unary): 147 | item.end = get_newline() + padding + item.end 148 | 149 | if item.has_tags_in_content() or (force_nl and not item.has_children() and not is_unary): 150 | item.start += get_newline() + padding + get_indentation() 151 | 152 | elif item.is_inline() and has_block_sibling(item) and not is_very_first_child(item): 153 | item.start = get_newline() + padding + item.start 154 | elif item.is_inline() and item.has_block_children(): 155 | item.end = get_newline() + padding + item.end 156 | 157 | item.padding = padding + get_indentation() 158 | 159 | return item 160 | 161 | @zencoding.filter('_format') 162 | def process(tree, profile, level=0): 163 | """ 164 | Processes simplified tree, making it suitable for output as HTML structure 165 | @type item: ZenNode 166 | @type profile: dict 167 | @param level: Depth level 168 | @type level: int 169 | """ 170 | 171 | for item in tree.children: 172 | if item.type == 'tag': 173 | item = process_tag(item, profile, level) 174 | else: 175 | item = process_snippet(item, profile, level) 176 | 177 | if item.content: 178 | item.content = zencoding.utils.pad_string(item.content, item.padding) 179 | 180 | process(item, profile, level + 1) 181 | 182 | return tree -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/haml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Filter that produces HAML tree 6 | @author Sergey Chikuyonok (serge.che@gmail.com) 7 | @link http://chikuyonok.ru 8 | ''' 9 | import zencoding.utils 10 | 11 | child_token = '${child}' 12 | tabstops = [0] 13 | 14 | def make_attributes_string(tag, profile): 15 | """ 16 | Creates HTML attributes string from tag according to profile settings 17 | @type tag: ZenNode 18 | @type profile: dict 19 | """ 20 | # make attribute string 21 | attrs = '' 22 | attr_quote = profile['attr_quotes'] == 'single' and "'" or '"' 23 | cursor = profile['place_cursor'] and zencoding.utils.get_caret_placeholder() or '' 24 | 25 | # use short notation for ID and CLASS attributes 26 | for a in tag.attributes: 27 | name_lower = a['name'].lower() 28 | if name_lower == 'id': 29 | attrs += '#' + (a['value'] or cursor) 30 | elif name_lower == 'class': 31 | attrs += '.' + (a['value'] or cursor) 32 | 33 | other_attrs = [] 34 | 35 | # process other attributes 36 | for a in tag.attributes: 37 | name_lower = a['name'].lower() 38 | if name_lower != 'id' and name_lower != 'class': 39 | attr_name = profile['attr_case'] == 'upper' and a['name'].upper() or name_lower 40 | other_attrs.append(':' + attr_name + ' => ' + attr_quote + (a['value'] or cursor) + attr_quote) 41 | 42 | if other_attrs: 43 | attrs += '{' + ', '.join(other_attrs) + '}' 44 | 45 | return attrs 46 | 47 | def _replace(placeholder, value): 48 | if placeholder: 49 | return placeholder % value 50 | else: 51 | return value 52 | 53 | def process_snippet(item, profile, level=0): 54 | """ 55 | Processes element with snippet type 56 | @type item: ZenNode 57 | @type profile: dict 58 | @type level: int 59 | """ 60 | data = item.source.value 61 | 62 | if not data: 63 | # snippet wasn't found, process it as tag 64 | return process_tag(item, profile, level) 65 | 66 | tokens = data.split(child_token) 67 | if len(tokens) < 2: 68 | start = tokens[0] 69 | end = '' 70 | else: 71 | start, end = tokens 72 | 73 | padding = item.parent and item.parent.padding or '' 74 | 75 | item.start = _replace(item.start, zencoding.utils.pad_string(start, padding)) 76 | item.end = _replace(item.end, zencoding.utils.pad_string(end, padding)) 77 | 78 | # replace variables ID and CLASS 79 | def cb(m): 80 | if m.group(1) == 'id' or m.group(1) == 'class': 81 | return item.get_attribute(m.group(1)) 82 | else: 83 | return m.group(0) 84 | 85 | item.start = zencoding.utils.replace_variables(item.start, cb) 86 | item.end = zencoding.utils.replace_variables(item.end, cb) 87 | 88 | return item 89 | 90 | def has_block_sibling(item): 91 | """ 92 | Test if passed node has block-level sibling element 93 | @type item: ZenNode 94 | @return: bool 95 | """ 96 | return item.parent and item.parent.has_block_children() 97 | 98 | def process_tag(item, profile, level=0): 99 | """ 100 | Processes element with tag type 101 | @type item: ZenNode 102 | @type profile: dict 103 | @type level: int 104 | """ 105 | if not item.name: 106 | # looks like it's root element 107 | return item 108 | 109 | attrs = make_attributes_string(item, profile) 110 | cursor = profile['place_cursor'] and zencoding.utils.get_caret_placeholder() or '' 111 | self_closing = '' 112 | is_unary = item.is_unary() and not item.children 113 | 114 | if profile['self_closing_tag'] and is_unary: 115 | self_closing = '/' 116 | 117 | # define tag name 118 | tag_name = '%' + (profile['tag_case'] == 'upper' and item.name.upper() or item.name.lower()) 119 | 120 | if tag_name.lower() == '%div' and '{' not in attrs: 121 | # omit div tag 122 | tag_name = '' 123 | 124 | item.end = '' 125 | item.start = _replace(item.start, tag_name + attrs + self_closing) 126 | 127 | if not item.children and not is_unary: 128 | item.start += cursor 129 | 130 | return item 131 | 132 | @zencoding.filter('haml') 133 | def process(tree, profile, level=0): 134 | """ 135 | Processes simplified tree, making it suitable for output as HTML structure 136 | @type tree: ZenNode 137 | @type profile: dict 138 | @type level: int 139 | """ 140 | if level == 0: 141 | # preformat tree 142 | tree = zencoding.run_filters(tree, profile, '_format') 143 | tabstops[0] = 0 144 | 145 | for item in tree.children: 146 | if item.type == 'tag': 147 | process_tag(item, profile, level) 148 | else: 149 | process_snippet(item, profile, level) 150 | 151 | # replace counters 152 | counter = zencoding.utils.get_counter_for_node(item) 153 | item.start = zencoding.utils.unescape_text(zencoding.utils.replace_counter(item.start, counter)) 154 | item.end = zencoding.utils.unescape_text(zencoding.utils.replace_counter(item.end, counter)) 155 | 156 | tabstops[0] += zencoding.utils.upgrade_tabstops(item, tabstops[0]) + 1 157 | 158 | process(item, profile, level + 1) 159 | 160 | return tree -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/html.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Filter that produces HTML tree 6 | @author Sergey Chikuyonok (serge.che@gmail.com) 7 | @link http://chikuyonok.ru 8 | ''' 9 | import zencoding.utils 10 | 11 | child_token = '${child}' 12 | tabstops = [0] 13 | 14 | def process_string_case(val, case_param=''): 15 | """ 16 | Returns proper string case, depending on profile value 17 | @param val: String to process 18 | @type val: str 19 | @param case_param: Profile's case value ('lower', 'upper', 'leave') 20 | @type case_param: str 21 | """ 22 | case_param = case_param.lower() 23 | if case_param == 'lower': 24 | return val.lower() 25 | elif case_param == 'upper': 26 | return val.upper() 27 | else: 28 | return val; 29 | 30 | def make_attributes_string(tag, profile): 31 | """ 32 | Creates HTML attributes string from tag according to profile settings 33 | @type tag: ZenNode 34 | @type profile: dict 35 | """ 36 | # make attribute string 37 | attrs = '' 38 | attr_quote = profile['attr_quotes'] == 'single' and "'" or '"' 39 | cursor = profile['place_cursor'] and zencoding.utils.get_caret_placeholder() or '' 40 | 41 | # process other attributes 42 | for a in tag.attributes: 43 | attr_name = process_string_case(a['name'], profile['attr_case']); 44 | attrs += ' ' + attr_name + '=' + attr_quote + (a['value'] or cursor) + attr_quote 45 | 46 | return attrs 47 | 48 | def _replace(placeholder, value): 49 | if placeholder: 50 | return placeholder % value 51 | else: 52 | return value 53 | 54 | def process_snippet(item, profile, level): 55 | """ 56 | Processes element with snippet type 57 | @type item: ZenNode 58 | @type profile: dict 59 | @type level: int 60 | """ 61 | data = item.source.value; 62 | 63 | if not data: 64 | # snippet wasn't found, process it as tag 65 | return process_tag(item, profile, level) 66 | 67 | tokens = data.split(child_token) 68 | if len(tokens) < 2: 69 | start = tokens[0] 70 | end = '' 71 | else: 72 | start, end = tokens 73 | 74 | padding = item.parent and item.parent.padding or '' 75 | 76 | item.start = _replace(item.start, zencoding.utils.pad_string(start, padding)) 77 | item.end = _replace(item.end, zencoding.utils.pad_string(end, padding)) 78 | 79 | # replace variables ID and CLASS 80 | def cb(m): 81 | if m.group(1) == 'id' or m.group(1) == 'class': 82 | return item.get_attribute(m.group(1)) 83 | else: 84 | return m.group(0) 85 | 86 | item.start = zencoding.utils.replace_variables(item.start, cb) 87 | item.end = zencoding.utils.replace_variables(item.end, cb) 88 | 89 | return item 90 | 91 | 92 | def has_block_sibling(item): 93 | """ 94 | Test if passed node has block-level sibling element 95 | @type item: ZenNode 96 | @return: bool 97 | """ 98 | return item.parent and item.parent.has_block_children() 99 | 100 | def process_tag(item, profile, level): 101 | """ 102 | Processes element with tag type 103 | @type item: ZenNode 104 | @type profile: dict 105 | @type level: int 106 | """ 107 | if not item.name: 108 | # looks like it's root element 109 | return item 110 | 111 | attrs = make_attributes_string(item, profile) 112 | cursor = profile['place_cursor'] and zencoding.utils.get_caret_placeholder() or '' 113 | self_closing = '' 114 | is_unary = item.is_unary() and not item.children 115 | start= '' 116 | end = '' 117 | 118 | if profile['self_closing_tag'] == 'xhtml': 119 | self_closing = ' /' 120 | elif profile['self_closing_tag'] is True: 121 | self_closing = '/' 122 | 123 | # define opening and closing tags 124 | tag_name = process_string_case(item.name, profile['tag_case']) 125 | if is_unary: 126 | start = '<' + tag_name + attrs + self_closing + '>' 127 | item.end = '' 128 | else: 129 | start = '<' + tag_name + attrs + '>' 130 | end = '' 131 | 132 | item.start = _replace(item.start, start) 133 | item.end = _replace(item.end, end) 134 | 135 | if not item.children and not is_unary and cursor not in item.content: 136 | item.start += cursor 137 | 138 | return item 139 | 140 | @zencoding.filter('html') 141 | def process(tree, profile, level=0): 142 | """ 143 | Processes simplified tree, making it suitable for output as HTML structure 144 | @type tree: ZenNode 145 | @type profile: dict 146 | @type level: int 147 | """ 148 | if level == 0: 149 | # preformat tree 150 | tree = zencoding.run_filters(tree, profile, '_format') 151 | tabstops[0] = 0 152 | 153 | for item in tree.children: 154 | if item.type == 'tag': 155 | process_tag(item, profile, level) 156 | else: 157 | process_snippet(item, profile, level) 158 | 159 | # replace counters 160 | counter = zencoding.utils.get_counter_for_node(item) 161 | item.start = zencoding.utils.unescape_text(zencoding.utils.replace_counter(item.start, counter)) 162 | item.end = zencoding.utils.unescape_text(zencoding.utils.replace_counter(item.end, counter)) 163 | item.content = zencoding.utils.unescape_text(zencoding.utils.replace_counter(item.content, counter)); 164 | 165 | tabstops[0] += zencoding.utils.upgrade_tabstops(item, tabstops[0]) + 1 166 | 167 | process(item, profile, level + 1) 168 | 169 | return tree 170 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/single-line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | Output abbreviation on a single line (i.e. no line breaks) 5 | @author Sergey Chikuyonok (serge.che@gmail.com) 6 | @link http://chikuyonok.ru 7 | ''' 8 | import re 9 | import zencoding 10 | 11 | re_nl = re.compile(r'[\n\r]') 12 | re_pad = re.compile(r'^\s+') 13 | 14 | @zencoding.filter('s') 15 | def process(tree, profile): 16 | for item in tree.children: 17 | if item.type == 'tag': 18 | # remove padding from item 19 | item.start = re_pad.sub('', item.start) 20 | item.end = re_pad.sub('', item.end) 21 | 22 | # remove newlines 23 | item.start = re_nl.sub('', item.start) 24 | item.end = re_nl.sub('', item.end) 25 | item.content = re_nl.sub('', item.content) 26 | 27 | process(item, profile) 28 | 29 | return tree -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/trim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | Trim filter: removes characters at the beginning of the text 5 | content that indicates lists: numbers, #, *, -, etc. 6 | @author Sergey Chikuyonok (serge.che@gmail.com) 7 | @link http://chikuyonok.ru 8 | ''' 9 | import re 10 | import zencoding 11 | 12 | re_indicators = re.compile(r'^([\s|\u00a0])?[\d|#|\-|\*|\u2022]+\.?\s*') 13 | 14 | @zencoding.filter('t') 15 | def process(tree, profile): 16 | for item in tree.children: 17 | if item.content: 18 | item.content = re_indicators.sub('', item.content) 19 | 20 | process(item, profile) 21 | 22 | return tree -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/xsd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Filter that alters XSD attributes - adjusted from xsl.py 6 | @author Dave Brotherstone 7 | ''' 8 | 9 | import re 10 | import zencoding 11 | import Npp 12 | re_attr_id = re.compile('\\s+id\\s*=\\s*([\'"])(.*)\\1') 13 | re_attr_class = re.compile('\\s+class\\s*=\\s*([\'"])(.*)\\1') 14 | 15 | 16 | def replace_attrs(item): 17 | """ 18 | Replaces the attributes of item so id becomes name 19 | and class becomes type 20 | """ 21 | item.start = re_attr_id.sub(r" name=\1\2\1", item.start) 22 | item.start = re_attr_class.sub(r" type=\1\2\1", item.start) 23 | 24 | @zencoding.filter('xsd') 25 | def process(tree, profile): 26 | for item in tree.children: 27 | if item.type == 'tag': 28 | replace_attrs(item) 29 | 30 | process(item, profile) -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/filters/xsl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Filter for trimming "select" attributes from some tags that contains 6 | child elements 7 | @author Sergey Chikuyonok (serge.che@gmail.com) 8 | @link http://chikuyonok.ru 9 | ''' 10 | import re 11 | import zencoding 12 | 13 | tags = { 14 | 'xsl:variable': 1, 15 | 'xsl:with-param': 1 16 | } 17 | 18 | re_attr = re.compile(r'\s+select\s*=\s*([\'"]).*?\1') 19 | 20 | def trim_attribute(node): 21 | """ 22 | Removes "select" attribute from node 23 | @type node: ZenNode 24 | """ 25 | node.start = re_attr.sub('', node.start) 26 | 27 | @zencoding.filter('xsl') 28 | def process(tree, profile): 29 | for item in tree.children: 30 | if item.type == 'tag' and item.name.lower() in tags and item.children: 31 | trim_attribute(item) 32 | 33 | process(item, profile) -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/html_matcher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Context-independent xHTML pair matcher 6 | Use method match(html, start_ix) to find matching pair. 7 | If pair was found, this function returns a list of indexes where tag pair 8 | starts and ends. If pair wasn't found, None will be returned. 9 | 10 | The last matched (or unmatched) result is saved in last_match 11 | dictionary for later use. 12 | 13 | @author: Sergey Chikuyonok (serge.che@gmail.com) 14 | ''' 15 | import re 16 | 17 | start_tag = r'<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*)\s*(\/?)>' 18 | end_tag = r'<\/([\w\:\-]+)[^>]*>' 19 | attr = r'([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?' 20 | 21 | "Last matched HTML pair" 22 | last_match = { 23 | 'opening_tag': None, # Tag() or Comment() object 24 | 'closing_tag': None, # Tag() or Comment() object 25 | 'start_ix': -1, 26 | 'end_ix': -1 27 | } 28 | 29 | cur_mode = 'xhtml' 30 | "Current matching mode" 31 | 32 | def set_mode(new_mode): 33 | global cur_mode 34 | if new_mode != 'html': new_mode = 'xhtml' 35 | cur_mode = new_mode 36 | 37 | def make_map(elems): 38 | """ 39 | Create dictionary of elements for faster searching 40 | @param elems: Elements, separated by comma 41 | @type elems: str 42 | """ 43 | obj = {} 44 | for elem in elems.split(','): 45 | obj[elem] = True 46 | 47 | return obj 48 | 49 | # Empty Elements - HTML 4.01 50 | empty = make_map("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed"); 51 | 52 | # Block Elements - HTML 4.01 53 | block = make_map("address,applet,blockquote,button,center,dd,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul"); 54 | 55 | # Inline Elements - HTML 4.01 56 | inline = make_map("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); 57 | 58 | # Elements that you can, intentionally, leave open 59 | # (and which close themselves) 60 | close_self = make_map("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); 61 | 62 | # Attributes that have their values filled in disabled="disabled" 63 | fill_attrs = make_map("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); 64 | 65 | #Special Elements (can contain anything) 66 | # serge.che: parsing data inside elements is a "feature" 67 | special = make_map("style"); 68 | 69 | class Tag(): 70 | """Matched tag""" 71 | def __init__(self, match, ix): 72 | """ 73 | @type match: MatchObject 74 | @param match: Matched HTML tag 75 | @type ix: int 76 | @param ix: Tag's position 77 | """ 78 | global cur_mode 79 | 80 | name = match.group(1).lower() 81 | self.name = name 82 | self.full_tag = match.group(0) 83 | self.start = ix 84 | self.end = ix + len(self.full_tag) 85 | self.unary = ( len(match.groups()) > 2 and bool(match.group(3)) ) or (name in empty and cur_mode == 'html') 86 | self.type = 'tag' 87 | self.close_self = (name in close_self and cur_mode == 'html') 88 | 89 | class Comment(): 90 | "Matched comment" 91 | def __init__(self, start, end): 92 | self.start = start 93 | self.end = end 94 | self.type = 'comment' 95 | 96 | def make_range(opening_tag=None, closing_tag=None, ix=0): 97 | """ 98 | Makes selection ranges for matched tag pair 99 | @type opening_tag: Tag 100 | @type closing_tag: Tag 101 | @type ix: int 102 | @return list 103 | """ 104 | start_ix, end_ix = -1, -1 105 | 106 | if opening_tag and not closing_tag: # unary element 107 | start_ix = opening_tag.start 108 | end_ix = opening_tag.end 109 | elif opening_tag and closing_tag: # complete element 110 | if (opening_tag.start < ix and opening_tag.end > ix) or (closing_tag.start <= ix and closing_tag.end > ix): 111 | start_ix = opening_tag.start 112 | end_ix = closing_tag.end; 113 | else: 114 | start_ix = opening_tag.end 115 | end_ix = closing_tag.start 116 | 117 | return start_ix, end_ix 118 | 119 | def save_match(opening_tag=None, closing_tag=None, ix=0): 120 | """ 121 | Save matched tag for later use and return found indexes 122 | @type opening_tag: Tag 123 | @type closing_tag: Tag 124 | @type ix: int 125 | @return list 126 | """ 127 | last_match['opening_tag'] = opening_tag; 128 | last_match['closing_tag'] = closing_tag; 129 | 130 | last_match['start_ix'], last_match['end_ix'] = make_range(opening_tag, closing_tag, ix) 131 | 132 | return last_match['start_ix'] != -1 and (last_match['start_ix'], last_match['end_ix']) or (None, None) 133 | 134 | def match(html, start_ix, mode='xhtml'): 135 | """ 136 | Search for matching tags in html, starting from 137 | start_ix position. The result is automatically saved 138 | in last_match property 139 | """ 140 | return _find_pair(html, start_ix, mode, save_match) 141 | 142 | def find(html, start_ix, mode='xhtml'): 143 | """ 144 | Search for matching tags in html, starting from 145 | start_ix position. 146 | """ 147 | return _find_pair(html, start_ix, mode) 148 | 149 | def get_tags(html, start_ix, mode='xhtml'): 150 | """ 151 | Search for matching tags in html, starting from 152 | start_ix position. The difference between 153 | match function itself is that get_tags 154 | method doesn't save matched result in last_match property 155 | and returns array of opening and closing tags 156 | This method is generally used for lookups 157 | """ 158 | return _find_pair(html, start_ix, mode, lambda op, cl=None, ix=0: (op, cl) if op and op.type == 'tag' else None) 159 | 160 | 161 | def _find_pair(html, start_ix, mode='xhtml', action=make_range): 162 | """ 163 | Search for matching tags in html, starting from 164 | start_ix position 165 | 166 | @param html: Code to search 167 | @type html: str 168 | 169 | @param start_ix: Character index where to start searching pair 170 | (commonly, current caret position) 171 | @type start_ix: int 172 | 173 | @param action: Function that creates selection range 174 | @type action: function 175 | 176 | @return: list 177 | """ 178 | 179 | forward_stack = [] 180 | backward_stack = [] 181 | opening_tag = None 182 | closing_tag = None 183 | html_len = len(html) 184 | 185 | set_mode(mode) 186 | 187 | def has_match(substr, start=None): 188 | if start is None: 189 | start = ix 190 | 191 | return html.find(substr, start) == start 192 | 193 | 194 | def find_comment_start(start_pos): 195 | while start_pos: 196 | if html[start_pos] == '<' and has_match('') + ix + 3; 230 | if ix < start_ix and end_ix >= start_ix: 231 | return action(Comment(ix, end_ix)) 232 | elif ch == '-' and has_match('-->'): # found comment end 233 | # search left until comment start is reached 234 | ix = find_comment_start(ix) 235 | 236 | ix -= 1 237 | 238 | if not opening_tag: 239 | return action(None) 240 | 241 | # find closing tag 242 | if not closing_tag: 243 | ix = start_ix 244 | while ix < html_len: 245 | ch = html[ix] 246 | if ch == '<': 247 | check_str = html[ix:] 248 | m = re.match(start_tag, check_str) 249 | if m: # found opening tag 250 | tmp_tag = Tag(m, ix); 251 | if not tmp_tag.unary: 252 | forward_stack.append(tmp_tag) 253 | else: 254 | m = re.match(end_tag, check_str) 255 | if m: #found closing tag 256 | tmp_tag = Tag(m, ix); 257 | if forward_stack and forward_stack[-1].name == tmp_tag.name: 258 | forward_stack.pop() 259 | else: # found matched closing tag 260 | closing_tag = tmp_tag; 261 | break 262 | elif has_match('') + 2 264 | continue 265 | elif ch == '-' and has_match('-->'): 266 | # looks like cursor was inside comment with invalid HTML 267 | if not forward_stack or forward_stack[-1].type != 'comment': 268 | end_ix = ix + 3 269 | return action(Comment( find_comment_start(ix), end_ix )) 270 | 271 | ix += 1 272 | 273 | return action(opening_tag, closing_tag, start_ix) -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruderstein/ZenCoding-Python/d291792c6ab154e6ffe41781eab222a0eaeccbaf/ZenCodingPython/zencoding/interface/__init__.py -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/interface/editor.py: -------------------------------------------------------------------------------- 1 | ''' 2 | High-level editor interface that communicates with underlying editor (like 3 | Espresso, Coda, etc.) or browser. 4 | Basically, you should call set_context(obj) method to 5 | set up undelying editor context before using any other method. 6 | 7 | This interface is used by zen_actions.py for performing different 8 | actions like Expand abbreviation 9 | 10 | @example 11 | from zencoding.interface.editor import ZenEditor 12 | editor = ZenEditor(context) 13 | //now you are ready to use editor object 14 | editor.get_selection_range(); 15 | 16 | @author Sergey Chikuyonok (serge.che@gmail.com) 17 | @link http://chikuyonok.ru 18 | ''' 19 | class ZenEditor(): 20 | def __init__(self): 21 | pass 22 | 23 | def set_context(self, context): 24 | """ 25 | Setup underlying editor context. You should call this method 26 | before using any Zen Coding action. 27 | @param context: context object 28 | """ 29 | pass 30 | 31 | def get_selection_range(self): 32 | """ 33 | Returns character indexes of selected text 34 | @return: list of start and end indexes 35 | @example 36 | start, end = zen_editor.get_selection_range(); 37 | print('%s, %s' % (start, end)) 38 | """ 39 | return 0, 0 40 | 41 | 42 | def create_selection(self, start, end=None): 43 | """ 44 | Creates selection from start to end character 45 | indexes. If end is ommited, this method should place caret 46 | and start index 47 | @type start: int 48 | @type end: int 49 | @example 50 | zen_editor.create_selection(10, 40) 51 | # move caret to 15th character 52 | zen_editor.create_selection(15) 53 | """ 54 | pass 55 | 56 | def get_current_line_range(self): 57 | """ 58 | Returns current line's start and end indexes 59 | @return: list of start and end indexes 60 | @example 61 | start, end = zen_editor.get_current_line_range(); 62 | print('%s, %s' % (start, end)) 63 | """ 64 | return 0, 0 65 | 66 | def get_caret_pos(self): 67 | """ Returns current caret position """ 68 | return 0 69 | 70 | def set_caret_pos(self, pos): 71 | """ 72 | Set new caret position 73 | @type pos: int 74 | """ 75 | pass 76 | 77 | def get_current_line(self): 78 | """ 79 | Returns content of current line 80 | @return: str 81 | """ 82 | return '' 83 | 84 | def replace_content(self, value, start=None, end=None): 85 | """ 86 | Replace editor's content or it's part (from start to 87 | end index). If value contains 88 | caret_placeholder, the editor will put caret into 89 | this position. If you skip start and end 90 | arguments, the whole target's content will be replaced with 91 | value. 92 | 93 | If you pass start argument only, 94 | the value will be placed at start string 95 | index of current content. 96 | 97 | If you pass start and end arguments, 98 | the corresponding substring of current target's content will be 99 | replaced with value 100 | @param value: Content you want to paste 101 | @type value: str 102 | @param start: Start index of editor's content 103 | @type start: int 104 | @param end: End index of editor's content 105 | @type end: int 106 | """ 107 | pass 108 | 109 | def get_content(self): 110 | """ 111 | Returns editor's content 112 | @return: str 113 | """ 114 | return '' 115 | 116 | def get_syntax(self): 117 | """ 118 | Returns current editor's syntax mode 119 | @return: str 120 | """ 121 | return 'html' 122 | 123 | def get_profile_name(self): 124 | """ 125 | Returns current output profile name (@see zen_coding#setup_profile) 126 | @return {String} 127 | """ 128 | return 'xhtml' 129 | 130 | def prompt(self, title): 131 | """ 132 | Ask user to enter something 133 | @param title: Dialog title 134 | @type title: str 135 | @return: Entered data 136 | @since: 0.65 137 | """ 138 | return '' 139 | 140 | def get_selection(self): 141 | """ 142 | Returns current selection 143 | @return: str 144 | @since: 0.65 145 | """ 146 | return '' 147 | 148 | def get_file_path(self): 149 | """ 150 | Returns current editor's file path 151 | @return: str 152 | @since: 0.65 153 | """ 154 | return '' 155 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/interface/file.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Sergey Chikuyonok (serge.che@gmail.com) 3 | @link http://chikuyonok.ru 4 | ''' 5 | import os.path 6 | 7 | def read(path): 8 | """ 9 | Read file content and return it 10 | @param path: File's relative or absolute path 11 | @type path: str 12 | @return: str 13 | """ 14 | content = None 15 | try: 16 | fp = open(path, 'rb') 17 | content = fp.read() 18 | fp.close() 19 | except: 20 | pass 21 | 22 | return content 23 | 24 | def locate_file(editor_file, file_name): 25 | """ 26 | Locate file_name file that relates to editor_file. 27 | File name may be absolute or relative path 28 | 29 | @type editor_file: str 30 | @type file_name: str 31 | @return String or None if file_name cannot be located 32 | """ 33 | result = None 34 | 35 | previous_parent = '' 36 | parent = os.path.dirname(editor_file) 37 | while parent and os.path.exists(parent) and parent != previous_parent: 38 | tmp = create_path(parent, file_name) 39 | if os.path.exists(tmp): 40 | result = tmp 41 | break 42 | 43 | previous_parent = parent 44 | parent = os.path.dirname(parent) 45 | 46 | return result 47 | 48 | def create_path(parent, file_name): 49 | """ 50 | Creates absolute path by concatenating parent and file_name. 51 | If parent points to file, its parent directory is used 52 | 53 | @type parent: str 54 | @type file_name: str 55 | @return: str 56 | """ 57 | result = '' 58 | file_name = file_name.lstrip('/') 59 | 60 | if os.path.exists(parent): 61 | if os.path.isfile(parent): 62 | parent = os.path.dirname(parent) 63 | 64 | result = os.path.normpath(os.path.join(parent, file_name)) 65 | 66 | return result 67 | 68 | def save(file, content): 69 | """ 70 | Saves content as file 71 | 72 | @param file: File's asolute path 73 | @type file: str 74 | @param content: File content 75 | @type content: str 76 | """ 77 | try: 78 | fp = open(file, 'wb') 79 | except: 80 | fdirs, fname = os.path.split(file) 81 | if fdirs: 82 | os.makedirs(fdirs) 83 | fp = open(file, 'wb') 84 | 85 | fp.write(content) 86 | fp.close() 87 | 88 | def get_ext(file): 89 | """ 90 | Returns file extention in lower case 91 | @type file: str 92 | @return: str 93 | """ 94 | ext = os.path.splitext(file)[1] 95 | if ext: 96 | ext = ext[1:] 97 | 98 | return ext.lower() 99 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/my_zen_settings.py: -------------------------------------------------------------------------------- 1 | my_zen_settings = { 2 | 'html': { 3 | 'abbreviations': { 4 | 'jq': '', 5 | 'demo': '
' 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruderstein/ZenCoding-Python/d291792c6ab154e6ffe41781eab222a0eaeccbaf/ZenCodingPython/zencoding/parser/__init__.py -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/parser/abbreviation.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Sergey Chikuyonok (serge.che@gmail.com) 3 | @link http://chikuyonok.ru 4 | ''' 5 | import re 6 | 7 | re_word = re.compile(r'^[\w\-:\$]+') 8 | re_attr_string = re.compile(r'^(["\'])((?:(?!\1)[^\\]|\\.)*)\1') 9 | re_valid_name = re.compile(r'^[\w\d\-_\$\:@!]+\+?$', re.IGNORECASE) 10 | 11 | def char_at(text, pos): 12 | """ 13 | Returns character at specified index of text. 14 | If index if out of range, returns empty string 15 | """ 16 | return text[pos] if pos < len(text) else '' 17 | 18 | def split_expression(expr): 19 | """ 20 | Split expression by node name and its content, if exists. E.g. if we pass 21 | 'a{Text}' expression, it will be splitted into 'a' and 'Text' 22 | 23 | @type expr: str 24 | @return: Result tuple with two elements: node name and its content 25 | """ 26 | # fast test on text node 27 | if '{' not in expr: 28 | return expr, None 29 | 30 | attr_lvl = 0 31 | text_lvl = 0 32 | brace_stack = [] 33 | i = 0 34 | il = len(expr) 35 | 36 | while i < il: 37 | ch = expr[i] 38 | if ch == '[': 39 | if not text_lvl: 40 | attr_lvl += 1 41 | elif ch == ']': 42 | if not text_lvl: 43 | attr_lvl -= 1 44 | elif ch == '{': 45 | if not attr_lvl: 46 | text_lvl += 1 47 | brace_stack.append(i) 48 | elif ch == '}': 49 | if not attr_lvl: 50 | text_lvl -= 1 51 | brace_start = brace_stack.pop() 52 | if text_lvl == 0: 53 | # found braces bounds 54 | return expr[0:brace_start], expr[brace_start + 1:i] 55 | i += 1 56 | 57 | # if we are here, then no valid text node found 58 | return expr, None 59 | 60 | def parse_attributes(s): 61 | """ 62 | Parses tag attributes extracted from abbreviation 63 | @type s: str 64 | 65 | Example of incoming data: 66 | #header 67 | .some.data 68 | .some.data#header 69 | [attr] 70 | #item[attr=Hello other="World"].class 71 | """ 72 | 73 | result = [] 74 | name = '' 75 | collect_name = True 76 | class_name = None 77 | char_map = {'#': 'id', '.': 'class'} 78 | 79 | # walk char-by-char 80 | i = 0 81 | il = len(s) 82 | 83 | while i < il: 84 | ch = s[i] 85 | if ch == '#': # id 86 | val = get_word(i, s[1:]) 87 | result.append({'name': char_map[ch], 'value': val}) 88 | i += len(val) + 1 89 | collect_name = False 90 | elif ch == '.': # class 91 | val = get_word(i, s[1:]) 92 | if not class_name: 93 | # remember object pointer for value modification 94 | class_name = {'name': char_map[ch], 'value': ''} 95 | result.append(class_name) 96 | 97 | class_name['value'] += (class_name['value'] and ' ' or '') + val 98 | i += len(val) + 1 99 | collect_name = False 100 | elif ch == '[': # begin attribute set 101 | # search for end of set 102 | try: 103 | end_ix = s.index(']', i) 104 | for a in extract_attributes(s[i + 1:end_ix]): 105 | result.append(a) 106 | 107 | i = end_ix 108 | except: 109 | # invalid attribute set, stop searching 110 | i = len(s) 111 | 112 | collect_name = False 113 | else: 114 | if collect_name: 115 | name += ch 116 | i += 1 117 | 118 | return name, result 119 | 120 | def get_word(ix, s): 121 | """ 122 | Get word, starting at 'ix' character of 's 123 | """ 124 | m = re_word.match(s[ix:]) 125 | return m and m.group(0) or '' 126 | 127 | def extract_attributes(attr_set): 128 | """ 129 | Extract attributes and their values from attribute set 130 | @type attr_set: str 131 | """ 132 | attr_set = attr_set.strip() 133 | loop_count = 100 # endless loop protection 134 | result = [] 135 | attr = None 136 | 137 | while attr_set and loop_count: 138 | attr_name = get_word(0, attr_set) 139 | attr = None 140 | if attr_name: 141 | attr = {'name': attr_name, 'value': ''} 142 | 143 | # let's see if attribute has value 144 | ch = char_at(attr_set, len(attr_name)) 145 | if ch == '=': 146 | ch2 = char_at(attr_set, len(attr_name) + 1) 147 | if ch2 == '"' or ch2 == "'": 148 | # we have a quoted string 149 | m = re_attr_string.match(attr_set[len(attr_name) + 1:]) 150 | if m: 151 | attr['value'] = m.group(2) 152 | attr_set = attr_set[len(attr_name) + len(m.group(0)) + 1:].strip() 153 | else: 154 | # something wrong, break loop 155 | attr_set = '' 156 | else: 157 | # unquoted string 158 | m = re.match(r'(.+?)(\s|$)', attr_set[len(attr_name) + 1:]) 159 | if m: 160 | attr['value'] = m.group(1) 161 | attr_set = attr_set[len(attr_name) + len(m.group(1)) + 1:].strip() 162 | else: 163 | # something wrong, break loop 164 | attr_set = '' 165 | else: 166 | attr_set = attr_set[len(attr_name):].strip() 167 | else: 168 | # something wrong, can't extract attribute name 169 | break; 170 | 171 | if attr: result.append(attr) 172 | loop_count -= 1 173 | 174 | return result 175 | 176 | def squash(node): 177 | """ 178 | Optimizes tree node: replaces empty nodes with their children 179 | @type node: TreeNode 180 | @return: TreeNode 181 | """ 182 | for i, child in enumerate(node.children): 183 | if child.is_empty(): 184 | node.children[i:i + 1] = child.children 185 | 186 | return node 187 | 188 | def optimize_tree(node): 189 | """ 190 | @type node: TreeNode 191 | @return: TreeNode 192 | """ 193 | while node.has_empty_children(): 194 | squash(node) 195 | 196 | for child in node.children: 197 | optimize_tree(child) 198 | 199 | return node 200 | 201 | def parse(abbr): 202 | """ 203 | Parses abbreviation into tree with respect of groups, 204 | text nodes and attributes. Each node of the tree is a single 205 | abbreviation. Tree represents actual structure of the outputted 206 | result 207 | @param abbr: Abbreviation to parse 208 | @type abbr: str 209 | @return: TreeNode 210 | """ 211 | root = TreeNode() 212 | context = root.add_child() 213 | i = 0 214 | il = len(abbr) 215 | text_lvl = 0 216 | attr_lvl = 0 217 | group_stack = [root] 218 | token = [''] 219 | 220 | def dump_token(): 221 | if token[0]: 222 | context.set_abbreviation(token[0]) 223 | token[0] = '' 224 | 225 | while i < il: 226 | ch = abbr[i] 227 | prev_ch = i and abbr[i - 1] or '' 228 | if ch == '{': 229 | if not attr_lvl: 230 | text_lvl += 1 231 | token[0] += ch 232 | elif ch == '}': 233 | if not attr_lvl: 234 | text_lvl -= 1 235 | token[0] += ch 236 | elif ch == '[': 237 | if not text_lvl: 238 | attr_lvl += 1 239 | token[0] += ch 240 | elif ch == ']': 241 | if not text_lvl: 242 | attr_lvl -= 1 243 | token[0] += ch 244 | elif ch == '(': 245 | if not text_lvl and not attr_lvl: 246 | # beginning of the new group 247 | dump_token(); 248 | 249 | if prev_ch != '+' and prev_ch != '>': 250 | # previous char is not an operator, assume it's 251 | # a sibling 252 | context = context.parent.add_child() 253 | 254 | group_stack.append(context) 255 | context = context.add_child() 256 | else: 257 | token[0] += ch 258 | elif ch == ')': 259 | if not text_lvl and not attr_lvl: 260 | # end of the group, pop stack 261 | dump_token() 262 | context = group_stack.pop() 263 | 264 | if i < il - 1 and char_at(abbr, i + 1) == '*': 265 | # group multiplication 266 | group_mul = '' 267 | for j in xrange(i + 2, il): 268 | n_ch = abbr[j] 269 | if n_ch.isdigit(): 270 | group_mul += n_ch 271 | else: 272 | break 273 | 274 | i += len(group_mul) + 1 275 | group_mul = int(group_mul or 1) 276 | while 1 < group_mul: 277 | context.parent.add_child(context) 278 | group_mul -= 1 279 | else: 280 | token[0] += ch 281 | elif ch == '+': # sibling operator 282 | if not text_lvl and not attr_lvl and i != il - 1: 283 | dump_token() 284 | context = context.parent.add_child() 285 | else: 286 | token[0] += ch 287 | elif ch == '>': # child operator 288 | if not text_lvl and not attr_lvl: 289 | dump_token() 290 | context = context.add_child() 291 | else: 292 | token[0] += ch 293 | else: 294 | token[0] += ch 295 | 296 | i += 1 297 | 298 | # put the final token 299 | dump_token() 300 | return optimize_tree(root) 301 | 302 | class TreeNode(object): 303 | re_multiplier = re.compile(r'\*(\d+)?$') 304 | 305 | def __init__(self, parent=None): 306 | self.abbreviation = ''; 307 | self.parent = parent 308 | self.children = [] 309 | self.count = 1 310 | self.name = None 311 | self.text = None 312 | self.attributes = [] 313 | self.is_repeating = False 314 | self.has_implicit_name = False 315 | 316 | def add_child(self, child=None): 317 | """ 318 | Adds passed or creates new child 319 | @type child: TreeNode 320 | @return: TreeNode 321 | """ 322 | if not child: child = TreeNode() 323 | child.parent = self 324 | self.children.append(child) 325 | return child 326 | 327 | def replace(self, node): 328 | """ 329 | Replace current node in parent's child list with another node 330 | @type node: TreeNode 331 | """ 332 | if self.parent: 333 | children = self.parent.children 334 | if self in children: 335 | children[children.index(self)] = node 336 | self.parent = None 337 | return 338 | 339 | def set_abbreviation(self, abbr): 340 | """ 341 | Sets abbreviation that belongs to current node 342 | @type abbr: str 343 | """ 344 | self.abbreviation = abbr 345 | m = self.re_multiplier.search(abbr) 346 | if m: 347 | self.count = m.group(1) and int(m.group(1)) or 1 348 | self.is_repeating = not m.group(1) 349 | abbr = abbr[0:-len(m.group(0))] 350 | 351 | if abbr: 352 | name, self.text = split_expression(abbr) 353 | 354 | if name: 355 | self.name, self.attributes = parse_attributes(name) 356 | if not self.name: 357 | self.name = 'div' 358 | self.has_implicit_name = True 359 | 360 | # validate name 361 | if self.name and not re_valid_name.match(self.name): 362 | raise ZenInvalidAbbreviation('self.name') 363 | 364 | def get_abbreviation(self): 365 | return self.expr 366 | 367 | def to_string(self, level=0): 368 | """ 369 | Dump current tree node into a foramtted string 370 | """ 371 | output = '(empty)' 372 | if self.abbreviation: 373 | output = '' 374 | if self.name: 375 | output = self.name 376 | 377 | if self.text is not None: 378 | output += (output and ' ' or '') + '{text: "' + self.text + '"}' 379 | 380 | if self.attributes: 381 | output += ' [' + ', '.join(['%s="%s"' % (a['name'], a['value']) for a in self.attributes]) + ']' 382 | 383 | result = ('-' * level) + output + '\n' 384 | for child in self.children: 385 | result += child.to_string(level + 1) 386 | 387 | return result 388 | 389 | def __repr__(self): 390 | return self.to_string() 391 | 392 | def has_empty_children(self): 393 | """ 394 | Check if current node contains children with empty expr 395 | property 396 | """ 397 | for child in self.children: 398 | if child.is_empty(): 399 | return True 400 | 401 | return False 402 | 403 | def is_empty(self): 404 | return not self.abbreviation 405 | 406 | def is_text_node(self): 407 | """ 408 | Check if current node is a text-only node 409 | """ 410 | return not self.name and self.text 411 | 412 | 413 | class ZenInvalidAbbreviation(Exception): 414 | """ 415 | Invalid abbreviation error 416 | @since: 0.7 417 | """ 418 | def __init__(self, value): 419 | self.value = value 420 | def __str__(self): 421 | return repr(self.value) -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/parser/css.py: -------------------------------------------------------------------------------- 1 | ''' 2 | A Python port of Stoyan Stefanov's CSSEX CSS parser 3 | 4 | How to use: 5 | call lex(source) to parse CSS source into tokens 6 | call to_source(tokens) to transform parsed tokens back to CSS source 7 | 8 | @link https://github.com/stoyan/etc/tree/master/cssex 9 | @author: Sergey Chikuyonok 10 | 11 | ''' 12 | 13 | class Walker(object): 14 | def __init__(self, source=None): 15 | self.lines = None 16 | self.total_lines = 0 17 | self.line = '' 18 | self.ch = '' 19 | self.linenum = -1 20 | self.chnum = -1 21 | 22 | if source is not None: 23 | self.init(source) 24 | 25 | def init(self, source): 26 | # source, yumm 27 | self.lines = source.splitlines() 28 | self.total_lines = len(self.lines) 29 | 30 | # reset 31 | self.chnum = -1 32 | self.linenum = -1 33 | self.ch = '' 34 | self.line = '' 35 | 36 | # advance 37 | self.next_line() 38 | self.next_char() 39 | 40 | 41 | def next_line(self): 42 | self.linenum += 1 43 | if self.total_lines <= self.linenum: 44 | self.line = False 45 | else: 46 | self.line = self.lines[self.linenum] 47 | 48 | if self.chnum != -1: 49 | self.chnum = 0 50 | 51 | return self.line 52 | 53 | def next_char(self): 54 | self.chnum += 1 55 | while get_char(self.line, self.chnum) is None: 56 | if self.next_line() is False: 57 | self.ch = False 58 | return False # end of source 59 | 60 | self.chnum = -1 61 | self.ch = '\n' 62 | return '\n' 63 | 64 | self.ch = self.line[self.chnum] 65 | return self.ch 66 | 67 | _walker = Walker() 68 | __tokens = [] 69 | 70 | # utility helpers 71 | def get_char(text, pos): 72 | if pos >= 0 and pos < len(text): 73 | return text[pos] 74 | else: 75 | return None 76 | 77 | def is_name_char(c): 78 | return c == '_' or c == '-' or c.isalpha() 79 | 80 | def is_op(ch, matchattr=None): 81 | if matchattr: 82 | return ch in '*^|$~' 83 | 84 | return ch in "{}[]()+*=.,;:>~|\\%$#@^!" 85 | 86 | def get_conf(): 87 | return { 88 | 'char': _walker.chnum, 89 | 'line': _walker.linenum 90 | } 91 | 92 | def tokener(value, token_type=None, c={}): 93 | "creates token objects and pushes them to a list" 94 | w = _walker 95 | __tokens.append({ 96 | 'charstart': c.get('char', w.chnum), 97 | 'charend': c.get('charend', w.chnum), 98 | 'linestart': c.get('line', w.linenum), 99 | 'lineend': c.get('lineend', w.linenum), 100 | 'value': value, 101 | 'type': token_type or value 102 | }) 103 | 104 | 105 | # oops 106 | class CSSEXError(Exception): 107 | def __init__(self, value, conf=None): 108 | self.value = value 109 | self.conf = conf or {} 110 | self.w = _walker 111 | 112 | def __str__(self): 113 | c = 'char' in self.conf and self.conf['char'] or self.w.chnum 114 | l = 'line' in self.conf and self.conf['line'] or self.w.linenum 115 | return "%s at line %d char %d" % (self.value, l + 1, c + 1) 116 | 117 | 118 | # token handlers follow for: 119 | # white space, comment, string, identifier, number, operator 120 | def white(): 121 | c = _walker.ch 122 | token = '' 123 | conf = get_conf() 124 | 125 | while c == " " or c == "\t": 126 | token += c 127 | c = _walker.next_char() 128 | 129 | 130 | tokener(token, 'white', conf) 131 | 132 | def comment(): 133 | w = _walker 134 | c = w.ch 135 | token = c 136 | conf = get_conf() 137 | 138 | cnext = w.next_char() 139 | 140 | if cnext != '*': 141 | # oops, not a comment, just a / 142 | conf['charend'] = conf['char'] 143 | conf['lineend'] = conf['line'] 144 | return tokener(token, token, conf) 145 | 146 | while not (c == "*" and cnext == "/"): 147 | token += cnext 148 | c = cnext 149 | cnext = w.next_char() 150 | 151 | token += cnext 152 | w.next_char() 153 | tokener(token, 'comment', conf) 154 | 155 | def str(): 156 | w = _walker 157 | c = w.ch 158 | q = c 159 | token = c 160 | conf = get_conf() 161 | 162 | c = w.next_char() 163 | 164 | while c != q: 165 | 166 | if c == '\n': 167 | cnext = w.next_char() 168 | if cnext == "\\": 169 | token += c + cnext 170 | else: 171 | # end of line with no \ escape = bad 172 | raise CSSEXError("Unterminated string", conf) 173 | 174 | else: 175 | if c == "\\": 176 | token += c + w.next_char() 177 | else: 178 | token += c 179 | 180 | c = w.next_char() 181 | 182 | token += c 183 | w.next_char() 184 | tokener(token, 'string', conf) 185 | 186 | def brace(): 187 | w = _walker 188 | c = w.ch 189 | depth = 0 190 | token = c 191 | conf = get_conf() 192 | 193 | c = w.next_char() 194 | 195 | while c != ')' and not depth: 196 | if c == '(': 197 | depth += 1 198 | elif c == ')': 199 | depth -= 1 200 | elif c is False: 201 | raise CSSEXError("Unterminated brace", conf) 202 | 203 | token += c 204 | c = w.next_char() 205 | 206 | token += c 207 | w.next_char() 208 | tokener(token, 'brace', conf) 209 | 210 | def identifier(pre=None): 211 | w = _walker 212 | c = w.ch 213 | conf = get_conf() 214 | token = pre and pre + c or c 215 | 216 | c = w.next_char() 217 | 218 | if pre: # adjust token position 219 | conf['char'] -= len(pre) 220 | 221 | while is_name_char(c) or c.isdigit(): 222 | token += c 223 | c = w.next_char() 224 | 225 | tokener(token, 'identifier', conf) 226 | 227 | def num(): 228 | w = _walker 229 | c = w.ch 230 | conf = get_conf() 231 | token = c 232 | point = token == '.' 233 | 234 | c = w.next_char() 235 | nondigit = not c.isdigit() 236 | 237 | # .2px or .classname? 238 | if point and nondigit: 239 | # meh, NaN, could be a class name, so it's an operator for now 240 | conf['charend'] = conf['char'] 241 | conf['lineend'] = conf['line'] 242 | return tokener(token, '.', conf) 243 | 244 | # -2px or -moz-something 245 | if token == '-' and nondigit: 246 | return identifier('-') 247 | 248 | while c is not False and (c.isdigit() or (not point and c == '.')): # not end of source && digit or first instance of . 249 | if c == '.': 250 | point = True 251 | 252 | token += c 253 | c = w.next_char() 254 | 255 | tokener(token, 'number', conf) 256 | 257 | def op(): 258 | w = _walker 259 | c = w.ch 260 | conf = get_conf() 261 | token = c 262 | next = w.next_char() 263 | 264 | if next == "=" and is_op(token, True): 265 | token += next 266 | tokener(token, 'match', conf) 267 | w.next_char() 268 | return 269 | 270 | conf['charend'] = conf['char'] + 1 271 | conf['lineend'] = conf['line'] 272 | tokener(token, token, conf) 273 | 274 | # call the appropriate handler based on the first character in a token suspect 275 | def tokenize(): 276 | ch = _walker.ch 277 | 278 | if ch == " " or ch == "\t": 279 | return white() 280 | 281 | if ch == '/': 282 | return comment() 283 | 284 | if ch == '"' or ch == "'": 285 | return str() 286 | 287 | if ch == '(': 288 | return brace() 289 | 290 | if ch == '-' or ch == '.' or ch.isdigit(): # tricky - char: minus (-1px) or dash (-moz-stuff) 291 | return num() 292 | 293 | if is_name_char(ch): 294 | return identifier() 295 | 296 | if is_op(ch): 297 | return op() 298 | 299 | if ch == "\n": 300 | tokener("line") 301 | _walker.next_char() 302 | return 303 | 304 | raise CSSEXError("Unrecognized character") 305 | 306 | def parse(source): 307 | """ 308 | Parse CSS source 309 | @type source: str 310 | """ 311 | _walker.init(source) 312 | globals()['__tokens'] = [] 313 | while _walker.ch is not False: 314 | tokenize() 315 | 316 | return __tokens 317 | 318 | 319 | def to_source(tokens): 320 | """ 321 | Transform parsed tokens to CSS source 322 | @type tokens: list 323 | """ 324 | src = '' 325 | for t in tokens: 326 | if t['type'] == 'line': 327 | src += '\n' 328 | else: 329 | src += t['value'] 330 | 331 | return src 332 | 333 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/parser/utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Sergey Chikuyonok (serge.che@gmail.com) 3 | @link http://chikuyonok.ru 4 | ''' 5 | from zencoding.parser import css, xml 6 | import re 7 | 8 | def is_stop_char(token): 9 | return token['type'] in '{};:' 10 | 11 | def char_at(text, pos): 12 | """ 13 | Returns character at specified index of text. 14 | If index if out of range, returns empty string 15 | """ 16 | return text[pos] if pos < len(text) else '' 17 | 18 | def calculate_nl_length(content, pos): 19 | """ 20 | Calculates newline width at specified position in content 21 | @param content: str 22 | @param pos: int 23 | @return: int 24 | """ 25 | if char_at(content, pos) == '\r' and char_at(content, pos + 1) == '\n': 26 | return 2 27 | 28 | return 1 29 | 30 | def post_process_optimized(optimized, original): 31 | """ 32 | Post-process optimized tokens: collapse tokens for complex values 33 | @param optimized: Optimized tokens 34 | @type optimized: list 35 | @param original: Original preprocessed tokens 36 | @type original: list 37 | """ 38 | for token in optimized: 39 | child = None 40 | if token['type'] == 'value': 41 | token['children'] = [] 42 | child = None 43 | 44 | subtoken_start = token['ref_start_ix'] 45 | 46 | while subtoken_start <= token['ref_end_ix']: 47 | subtoken = original[subtoken_start] 48 | if subtoken['type'] != 'white': 49 | if not child: 50 | child = [subtoken['start'], subtoken['end']] 51 | else: 52 | child[1] = subtoken['end'] 53 | elif child: 54 | token['children'].append(child) 55 | child = None 56 | 57 | subtoken_start += 1 58 | 59 | if child: # push last token 60 | token['children'].append(child) 61 | 62 | return optimized 63 | 64 | def make_token(type='', value='', pos=0, ix=0): 65 | value = value or '' 66 | return { 67 | 'type': type or '', 68 | 'content': value, 69 | 'start': pos, 70 | 'end': pos + len(value), 71 | # Reference token index that starts current token 72 | 'ref_start_ix': ix, 73 | # Reference token index that ends current token 74 | 'ref_end_ix': ix 75 | } 76 | 77 | def parse_css(source, offset=0): 78 | """ 79 | Parses CSS and optimizes parsed chunks 80 | @param source: CSS source code fragment 81 | @type source: str 82 | @param offset: Offset of CSS fragment inside whole document 83 | @type offset: int 84 | @return: list 85 | """ 86 | return optimize_css(css.parse(source), offset, source) 87 | 88 | def parse_html(tag, offset=0): 89 | """ 90 | Parses HTML and optimizes parsed chunks 91 | @param source: HTML source code fragment 92 | @type source: str 93 | @param offset: Offset of HTML fragment inside whole document 94 | @type offset: int 95 | @return: list 96 | """ 97 | tokens = xml.parse(tag) 98 | result = [] 99 | i = 0 100 | loop = 1000 # infinite loop protection 101 | 102 | try: 103 | while loop: 104 | loop -= 1 105 | t = tokens['next']() 106 | if not t: 107 | break 108 | else: 109 | result.append(make_token(t['style'], t['content'], offset + i, 0)) 110 | i += len(t['value']) 111 | except xml.StopIteration: 112 | pass 113 | 114 | return result 115 | 116 | class ExtList(list): 117 | def __init__(self): 118 | super(ExtList, self).__init__() 119 | self.original = [] 120 | 121 | 122 | def optimize_css(tokens, offset, content): 123 | """ 124 | Optimizes parsed CSS tokens: combines selector chunks, complex values 125 | into a single chunk 126 | @param tokens: Tokens produced by CSSEX.lex() 127 | @type tokens: list 128 | @param offset: CSS rule offset in source code (character index) 129 | @type offset: int 130 | @param content: Original CSS source code 131 | @type content: str 132 | @return: list of optimized tokens 133 | """ 134 | offset = offset or 0 135 | result = ExtList() 136 | _o = 0 137 | i = 0 138 | delta = 0 139 | in_rules = False 140 | in_value = False 141 | acc_tokens = { 142 | 'selector': None, 143 | 'value': None 144 | } 145 | orig_tokens = [] 146 | acc_type = None 147 | 148 | def add_token(token, type): 149 | if type and type in acc_tokens: 150 | if not acc_tokens[type]: 151 | acc_tokens[type] = make_token(type, token['value'], offset + delta + token['charstart'], i) 152 | result.append(acc_tokens[type]) 153 | else: 154 | acc_tokens[type]['content'] += token['value'] 155 | acc_tokens[type]['end'] += len(token['value']) 156 | acc_tokens[type]['ref_end_ix'] = i 157 | else: 158 | result.append(make_token(token['type'], token['value'], offset + delta + token['charstart'], i)) 159 | 160 | for i, token in enumerate(tokens): 161 | token = tokens[i] 162 | acc_type = None 163 | 164 | if token['type'] == 'line': 165 | delta += _o 166 | nl_size = content and calculate_nl_length(content, delta) or 1 167 | tok_value = nl_size == 1 and '\n' or '\r\n' 168 | 169 | orig_tokens.append(make_token(token['type'], tok_value, offset + delta)) 170 | 171 | result.append(make_token(token['type'], tok_value, offset + delta, i)) 172 | delta += nl_size 173 | _o = 0 174 | 175 | continue 176 | 177 | orig_tokens.append(make_token(token['type'], token['value'], offset + delta + token['charstart'])) 178 | 179 | # use charstart and length because of incorrect charend 180 | # computation for whitespace 181 | _o = token['charstart'] + len(token['value']) 182 | 183 | if token['type'] != 'white': 184 | if token['type'] == '{': 185 | in_rules = True 186 | acc_tokens['selector'] = None 187 | elif in_rules: 188 | if token['type'] == ':': 189 | in_value = True 190 | elif token['type'] == ';': 191 | in_value = False 192 | acc_tokens['value'] = None 193 | elif token['type'] == '}': 194 | in_value = in_rules = False 195 | acc_tokens['value'] = None 196 | elif in_value or acc_tokens['value']: 197 | acc_type = 'value' 198 | elif acc_tokens['selector'] or (not in_rules and not is_stop_char(token)): 199 | # start selector token 200 | acc_type = 'selector' 201 | 202 | add_token(token, acc_type) 203 | else: 204 | # whitespace token, decide where it should be 205 | if i < len(tokens) - 1 and is_stop_char(tokens[i + 1]): 206 | continue 207 | 208 | if acc_tokens['selector'] or acc_tokens['value']: 209 | add_token(token, acc_tokens['selector'] and 'selector' or 'value') 210 | 211 | result.original = orig_tokens 212 | return post_process_optimized(result, orig_tokens) 213 | 214 | def extract_css_rule(content, pos, is_backward=False): 215 | """ 216 | Extracts single CSS selector definition from source code 217 | @param {String} content CSS source code 218 | @type content: str 219 | @param pos: Character position where to start source code extraction 220 | @type pos: int 221 | """ 222 | result = '' 223 | c_len = len(content) 224 | offset = pos 225 | brace_pos = -1 226 | 227 | # search left until we find rule edge 228 | while offset >= 0: 229 | ch = content[offset] 230 | if ch == '{': 231 | brace_pos = offset 232 | break 233 | elif ch == '}' and not is_backward: 234 | offset += 1 235 | break 236 | 237 | offset -= 1 238 | 239 | # search right for full rule set 240 | while offset < c_len: 241 | ch = content[offset] 242 | if ch == '{': 243 | brace_pos = offset 244 | elif ch == '}': 245 | if brace_pos != -1: 246 | result = content[brace_pos:offset + 1] 247 | break 248 | 249 | offset += 1 250 | 251 | if result: 252 | # find CSS selector 253 | offset = brace_pos - 1 254 | selector = '' 255 | while offset >= 0: 256 | ch = content[offset] 257 | if ch in '{}/\\<>': break 258 | offset -= 1 259 | 260 | # also trim whitespace 261 | re_white = re.compile(r'^[\s\n\r]+', re.MULTILINE) 262 | selector = re.sub(re_white, '', content[offset + 1:brace_pos]) 263 | return (brace_pos - len(selector), brace_pos + len(result)) 264 | 265 | return None 266 | 267 | # function alias 268 | token = make_token -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/parser/xml.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file defines an XML parser, with a few kludges to make it 3 | usable for HTML. autoSelfClosers defines a set of tag names that 4 | are expected to not have a closing tag, and doNotIndent specifies 5 | the tags inside of which no indentation should happen (see Config 6 | object). These can be disabled by passing the editor an object like 7 | {useHTMLKludges: false} as parserConfig option. 8 | 9 | Original code by Marijn Haverbeke 10 | from CodeMirror project: http://codemirror.net/ 11 | 12 | ===== 13 | Run parse(text) method to parse HTML string 14 | ===== 15 | ''' 16 | import re 17 | 18 | class StopIteration(Exception): 19 | def __str__(self): 20 | return 'StopIteration' 21 | 22 | def is_white_space(ch): 23 | return ch != "\n" and ch.isspace() 24 | 25 | class Tokenizer(object): 26 | def __init__(self, source, state): 27 | self.state = state 28 | self.source = source 29 | 30 | def take(self, type): 31 | if isinstance(type, basestring): 32 | type = { 33 | 'style' : type, 34 | 'type' : type 35 | } 36 | 37 | if 'content' not in type: 38 | type['content'] = '' 39 | 40 | type['content'] += self.source.get() 41 | if not re.search(r'\n$', type['content']): 42 | self.source.next_while(is_white_space) 43 | 44 | type['value'] = type['content'] + self.source.get() 45 | return type 46 | 47 | def next(self): 48 | if not self.source.more(): 49 | raise StopIteration 50 | 51 | if self.source.equals("\n"): 52 | self.source.next() 53 | return self.take("whitespace") 54 | 55 | type = None 56 | if self.source.applies(is_white_space): 57 | type = "whitespace" 58 | else: 59 | def save(s): 60 | self.state = s 61 | 62 | while not type: 63 | type = self.state(self.source, save) 64 | 65 | return self.take(type) 66 | 67 | class TraverseDOM(object): 68 | def __init__(self, source): 69 | self.source = source 70 | self.__fed = False 71 | 72 | def next(self): 73 | if not self.__fed: 74 | self.__fed = True 75 | return self.source 76 | else: 77 | raise StopIteration 78 | 79 | class StringStream(object): 80 | """ 81 | String streams are the things fed to parsers (which can feed them to a 82 | tokenizer if they want). They provide peek and next methods for looking at 83 | the current character (next 'consumes' this character, peek does not), and a 84 | get method for retrieving all the text that was consumed since the last time 85 | get was called. 86 | 87 | An easy mistake to make is to let a StopIteration exception finish the token 88 | stream while there are still characters pending in the string stream (hitting 89 | the end of the buffer while parsing a token). To make it easier to detect 90 | such errors, the stringstreams throw an exception when this happens. 91 | """ 92 | 93 | def __init__(self, source): 94 | """ 95 | Make a stringstream stream out of an iterator that returns strings. 96 | This is applied to the result of traverseDOM (see codemirror.js), 97 | and the resulting stream is fed to the parser. 98 | """ 99 | self.source = source 100 | self.current = '' 101 | "String that's currently being iterated over." 102 | 103 | self.pos = 0 104 | "Position in that string." 105 | 106 | self.accum = "" 107 | "Accumulator for strings that have been iterated over but not get()-ed yet." 108 | 109 | # ZC fix: if we've passed a string, wrap it with traverseDOM-like interface 110 | if isinstance(source, basestring): 111 | self.source = TraverseDOM(source) 112 | 113 | def ensure_chars(self): 114 | "Make sure there are more characters ready, or throw StopIteration." 115 | while self.pos == len(self.current): 116 | self.accum += self.current 117 | self.current = "" # In case source.next() throws 118 | self.pos = 0 119 | try: 120 | self.current = self.source.next() 121 | except StopIteration: 122 | return False 123 | 124 | return True 125 | 126 | def peek(self): 127 | "Return the next character in the stream." 128 | if not self.ensure_chars(): 129 | return None 130 | 131 | return self.current[self.pos] 132 | 133 | def next(self): 134 | """ 135 | Get the next character, throw StopIteration if at end, check 136 | for unused content. 137 | """ 138 | if not self.ensure_chars(): 139 | if len(self.accum) > 0: 140 | raise "End of stringstream reached without emptying buffer ('" + self.accum + "')." 141 | else: 142 | raise StopIteration 143 | 144 | result = self.pos < len(self.current) and self.current[self.pos] or None 145 | self.pos += 1 146 | return result 147 | 148 | def get(self): 149 | "Return the characters iterated over since the last call to .get()." 150 | temp = self.accum 151 | self.accum = "" 152 | if self.pos > 0: 153 | temp += self.current[0:self.pos] 154 | self.current = self.current[self.pos:] 155 | self.pos = 0 156 | 157 | return temp 158 | 159 | def push(self, str): 160 | "Push a string back into the stream." 161 | self.current = self.current[0:self.pos] + str + self.current[self.pos:] 162 | 163 | def look_ahead(self, str, consume, skip_spaces, case_insensitive): 164 | def cased(str): 165 | return case_insensitive and str.lower() or str 166 | 167 | str = cased(str) 168 | found = False 169 | 170 | _accum = self.accum 171 | _pos = self.pos 172 | if skip_spaces: 173 | self.next_while_matches(r'\s') 174 | 175 | while True: 176 | end = self.pos + len(str) 177 | left = len(self.current) - self.pos 178 | 179 | if end <= len(self.current): 180 | found = str == cased(self.current[self.pos:end]) 181 | self.pos = end 182 | break 183 | elif str[0:left] == cased(self.current[self.pos:]): 184 | self.accum += self.current 185 | self.current = "" 186 | try: 187 | self.current = self.source.next() 188 | except StopIteration: 189 | break 190 | 191 | self.pos = 0 192 | str = str[left:] 193 | else: 194 | break 195 | 196 | if not (found and consume): 197 | self.current = self.accum[len(_accum):] + self.current 198 | self.pos = _pos 199 | self.accum = _accum 200 | 201 | 202 | return found 203 | 204 | def look_ahead_regex(self, regex, consume): 205 | "Wont't match past end of line." 206 | if regex[0] != "^": 207 | raise Exception("Regexps passed to lookAheadRegex must start with ^") 208 | 209 | # Fetch the rest of the line 210 | while self.current.find("\n", self.pos) == -1: 211 | try: 212 | self.current += self.source.next() 213 | except StopIteration: 214 | break 215 | 216 | matched = re.match(regex, self.current[self.pos:]) 217 | if matched and consume: 218 | self.pos += len(matched.group(0)) 219 | 220 | return matched 221 | 222 | def more(self): 223 | "Produce true if the stream isn't empty." 224 | return self.peek() is not None 225 | 226 | def applies(self, test): 227 | next = self.peek() 228 | return next is not None and test(next) 229 | 230 | def next_while(self, test): 231 | next = self.peek() 232 | while next is not None and test(next): 233 | self.next() 234 | next = self.peek() 235 | 236 | def matches(self, regexp): 237 | next = self.peek() 238 | return next is not None and re.search(regexp, next) 239 | 240 | def next_while_matches(self, regexp): 241 | next = self.peek() 242 | while next is not None and re.search(regexp, next): 243 | self.next() 244 | next = self.peek() 245 | 246 | def equals(self, ch): 247 | return ch == self.peek() 248 | 249 | def end_of_line(self): 250 | next = self.peek() 251 | return next is None or next == "\n" 252 | 253 | Kludges = { 254 | 'autoSelfClosers': {"br": True, "img": True, "hr": True, "link": True, "input": True, 255 | "meta": True, "col": True, "frame": True, "base": True, "area": True}, 256 | 'doNotIndent': {"pre": True, "!cdata": True} 257 | } 258 | NoKludges = {'autoSelfClosers': {}, 'doNotIndent': {"!cdata": True}} 259 | UseKludges = Kludges 260 | alignCDATA = False 261 | 262 | def tokenize_xml(source, start_state=None): 263 | """ 264 | Simple stateful tokenizer for XML documents. Returns a 265 | MochiKit-style iterator, with a state property that contains a 266 | function encapsulating the current state. See tokenize.js. 267 | """ 268 | 269 | def in_text(source, set_state): 270 | ch = source.next() 271 | if ch == "<": 272 | if source.equals("!"): 273 | source.next(); 274 | if source.equals("["): 275 | if source.look_ahead("[CDATA[", True): 276 | set_state(in_block("xml-cdata", "]]>")) 277 | return None 278 | else: 279 | return "xml-text" 280 | elif source.look_ahead("--", True): 281 | set_state(in_block("xml-comment", "-->")) 282 | return None 283 | elif source.look_ahead("DOCTYPE", True): 284 | source.next_while_matches(r'[\w\._\-]') 285 | set_state(in_block("xml-doctype", ">")) 286 | return "xml-doctype" 287 | else: 288 | return "xml-text" 289 | elif source.equals("?"): 290 | source.next() 291 | source.next_while_matches(r'[\w\._\-]') 292 | set_state(in_block("xml-processing", "?>")) 293 | return "xml-processing" 294 | else: 295 | if source.equals("/"): 296 | source.next() 297 | set_state(in_tag) 298 | return "xml-punctuation" 299 | elif ch == "&": 300 | while not source.end_of_line(): 301 | if source.next() == ";": 302 | break; 303 | return "xml-entity"; 304 | else: 305 | source.next_while_matches(r'[^&<\n]') 306 | return "xml-text"; 307 | 308 | def in_tag(source, set_state): 309 | ch = source.next() 310 | if ch == ">": 311 | set_state(in_text) 312 | return "xml-punctuation"; 313 | elif re.match(r'[?\/]', ch) and source.equals(">"): 314 | source.next(); 315 | set_state(in_text) 316 | return "xml-punctuation" 317 | elif ch == "=": 318 | return "xml-punctuation" 319 | elif re.match(r'[\'"]', ch): 320 | set_state(in_attribute(ch)) 321 | return None 322 | else: 323 | source.next_while_matches(r'[^\s=<>\"\'\/?]') 324 | return "xml-name" 325 | 326 | def in_attribute(quote): 327 | def fn(source, set_state): 328 | while not source.end_of_line(): 329 | if source.next() == quote: 330 | set_state(in_tag) 331 | break 332 | return "xml-attribute"; 333 | 334 | return fn 335 | 336 | def in_block(style, terminator): 337 | def fn(source, set_state): 338 | while not source.end_of_line(): 339 | if source.look_ahead(terminator, True): 340 | set_state(in_text) 341 | break 342 | source.next() 343 | 344 | return style 345 | 346 | return fn 347 | 348 | return Tokenizer(source, start_state or in_text) 349 | 350 | def parse(source): 351 | "Parse HTML source" 352 | if isinstance(source, basestring): 353 | source = StringStream(source) 354 | 355 | tokens = tokenize_xml(source) 356 | token = [None] 357 | current_tag = None 358 | context = [None] 359 | consume = [False] 360 | 361 | def push(fs): 362 | i = len(fs) - 1 363 | while i >= 0: 364 | cc.append(fs[i]) 365 | i -= 1 366 | 367 | def cont(*args): 368 | push(args); 369 | consume[0] = True 370 | 371 | def _pass(*args): 372 | push(args) 373 | consume[0] = False 374 | 375 | def mark_err(): 376 | token[0]['style'] += " xml-error" 377 | 378 | def expect(text): 379 | def fn(style, content): 380 | if content == text: cont() 381 | else: mark_err() 382 | 383 | return fn 384 | 385 | def push_context(tagname): 386 | context[0] = { 387 | 'prev': context[0], 388 | 'name': tagname 389 | } 390 | 391 | def pop_context(): 392 | context[0] = context[0]['prev'] 393 | 394 | harmless_tokens = { 395 | "xml-text": True, 396 | "xml-entity": True, 397 | "xml-comment": True, 398 | "xml-processing": True, 399 | "xml-doctype": True 400 | } 401 | 402 | def base(*args): 403 | return _pass(element, base) 404 | 405 | def element(style, content): 406 | if content == "<": 407 | cont(tagname, attributes, endtag()) 408 | elif content == "")) 410 | elif style == "xml-cdata": 411 | if not context[0] or context[0]['name'] != "!cdata": 412 | push_context("!cdata") 413 | if re.search(r'\]\]>$', content): 414 | pop_context() 415 | cont() 416 | elif style in harmless_tokens: 417 | cont() 418 | else: 419 | mark_err() 420 | cont() 421 | 422 | def tagname(style, content): 423 | if style == "xml-name": 424 | current_tag = content.lower() 425 | token[0]['style'] = "xml-tagname" 426 | cont() 427 | else: 428 | current_tag = None 429 | _pass() 430 | 431 | def closetagname(style, content): 432 | if style == "xml-name": 433 | token[0]['style'] = "xml-tagname" 434 | if context[0] and content.lower() == context[0]['name']: 435 | pop_context() 436 | else: 437 | mark_err() 438 | cont() 439 | 440 | def endtag(*args): 441 | def fn(style, content): 442 | if content == "/>" or (content == ">" and current_tag in UseKludges['autoSelfClosers']): 443 | cont() 444 | elif content == ">": 445 | push_context(current_tag) 446 | cont() 447 | else: 448 | mark_err() 449 | cont() 450 | 451 | return fn 452 | 453 | def attributes(style, content): 454 | if style == "xml-name": 455 | token[0]['style'] = "xml-attname" 456 | cont(attribute, attributes) 457 | else: 458 | _pass() 459 | 460 | def attribute(style, content): 461 | if content == "=": 462 | cont(value) 463 | elif content == ">" or content == "/>": 464 | _pass(endtag) 465 | else: 466 | _pass() 467 | 468 | def value(style, content): 469 | if style == "xml-attribute": 470 | cont(value) 471 | else: 472 | _pass() 473 | 474 | def next(): 475 | token[0] = tokens.next() 476 | if token[0]['style'] == "whitespace" or token[0]['type'] == "xml-comment": 477 | return token[0] 478 | 479 | while True: 480 | consume[0] = False 481 | cc.pop()(token[0]['style'], token[0]['content']) 482 | if consume[0]: 483 | return token[0] 484 | 485 | cc = [base] 486 | return {'next': next} 487 | 488 | 489 | 490 | -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/resources.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | @author Sergey Chikuyonok (serge.che@gmail.com) 5 | @link http://chikuyonok.ru 6 | ''' 7 | import re 8 | import types 9 | from zencoding.zen_settings import zen_settings 10 | import imp 11 | import os.path 12 | 13 | TYPE_ABBREVIATION = 'zen-tag' 14 | TYPE_EXPANDO = 'zen-expando' 15 | 16 | TYPE_REFERENCE = 'zen-reference' 17 | "Reference to another abbreviation or tag" 18 | 19 | VOC_SYSTEM = 'system' 20 | VOC_USER = 'user' 21 | 22 | re_tag = re.compile(r'^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*([\'"]).*?\3)*)\s*(\/?)>') 23 | "Regular expression for XML tag matching" 24 | re_attrs = re.compile(r'([\w\:\-]+)\s*=\s*([\'"])(.*?)\2') 25 | 26 | vocabularies = {} 27 | vocabularies[VOC_SYSTEM] = {} 28 | vocabularies[VOC_USER] = {} 29 | 30 | def is_parsed(obj): 31 | """ 32 | Check if specified resource is parsed by Zen Coding 33 | @return: bool 34 | """ 35 | return obj and not isinstance(obj, types.StringTypes) 36 | 37 | def get_vocabulary(name): 38 | """ 39 | Returns resource vocabulary by its name 40 | @param name: Vocabulary name ('system' or 'user') 41 | @type name: str 42 | """ 43 | return name in vocabularies and vocabularies[name] or vocabularies[VOC_USER] 44 | 45 | def has_deep_key(obj, key): 46 | """ 47 | Check if obj dictionary contains deep key. For example, 48 | example, it will allow you to test existance of my_dict[key1][key2][key3], 49 | testing existance of my_dict[key1] first, then my_dict[key1][key2], 50 | and finally my_dict[key1][key2][key3] 51 | @param obj: Dictionary to test 52 | @param obj: dict 53 | @param key: Deep key to test. Can be list (like ['key1', 'key2', 'key3']) or 54 | string (like 'key1.key2.key3') 55 | @type key: list, tuple, str 56 | @return: bool 57 | """ 58 | if isinstance(key, str): 59 | key = key.split('.') 60 | 61 | last_obj = obj 62 | for v in key: 63 | if hasattr(last_obj, v): 64 | last_obj = getattr(last_obj, v) 65 | elif last_obj.has_key(v): 66 | last_obj = last_obj[v] 67 | else: 68 | return False 69 | 70 | return True 71 | 72 | 73 | def create_resource_chain(vocabulary, syntax, name): 74 | """ 75 | Creates resource inheritance chain for lookups 76 | @param voc: Resource vocabulary 77 | @type voc: str 78 | @param syntax: Syntax name 79 | @type syntax: str 80 | @param name: Resource name 81 | @type name: str 82 | @return: list 83 | """ 84 | voc = get_vocabulary(vocabulary) 85 | result = [] 86 | resource = None 87 | 88 | if voc and syntax in voc: 89 | resource = voc[syntax] 90 | if name in resource: 91 | result.append(resource[name]) 92 | 93 | 94 | # get inheritance definition 95 | # in case of user-defined vocabulary, resource dependency 96 | # may be defined in system vocabulary only, so we have to correctly 97 | # handle this case 98 | chain_source = None 99 | if resource and 'extends' in resource: 100 | chain_source = resource 101 | elif vocabulary == VOC_USER and has_deep_key(get_vocabulary(VOC_SYSTEM), [syntax, 'extends']): 102 | chain_source = get_vocabulary(VOC_SYSTEM)[syntax] 103 | 104 | if chain_source: 105 | if not is_parsed(chain_source['extends']): 106 | chain_source['extends'] = [v.strip() for v in chain_source['extends'].split(',')] 107 | 108 | # find resource in ancestors 109 | for type in chain_source['extends']: 110 | if has_deep_key(voc, [type, name]): 111 | result.append(voc[type][name]) 112 | 113 | return result 114 | 115 | def _get_subset(vocabulary, syntax, name): 116 | """ 117 | Get resource collection from settings vocbulary for specified syntax. 118 | It follows inheritance chain if resource wasn't directly found in 119 | syntax settings 120 | @param voc: Resource vocabulary 121 | @type voc: str 122 | @param syntax: Syntax name 123 | @type syntax: str 124 | @param name: Resource name 125 | @type name: str 126 | """ 127 | chain = create_resource_chain(vocabulary, syntax, name) 128 | return chain and chain[0] or None 129 | 130 | def get_parsed_item(vocabulary, syntax, name, item): 131 | """ 132 | Returns parsed item located in specified vocabulary by its syntax and 133 | name 134 | @param voc: Resource vocabulary 135 | @type voc: str 136 | @param syntax: Syntax name 137 | @type syntax: str 138 | @param name: Resource name 139 | @type name: str 140 | @param item: Abbreviation or snippet name 141 | @type item: str 142 | @return {Object|null} 143 | """ 144 | chain = create_resource_chain(vocabulary, syntax, name) 145 | 146 | for res in chain: 147 | if item in res: 148 | if name == 'abbreviations' and not is_parsed(res[item]): 149 | # parse abbreviation 150 | res[item] = parse_abbreviation(item, res[item]) 151 | 152 | return res[item] 153 | 154 | return None 155 | 156 | def make_expando(key, value): 157 | """ 158 | Make expando from string 159 | @type key: str 160 | @type value: str 161 | @return: Entry 162 | """ 163 | return Entry(TYPE_EXPANDO, key, value) 164 | 165 | def make_abbreviation(key, tag_name, attrs, is_empty=False): 166 | """ 167 | Make abbreviation from string 168 | @param key: Abbreviation key 169 | @type key: str 170 | @param tag_name: Expanded element's tag name 171 | @type tag_name: str 172 | @param attrs: Expanded element's attributes 173 | @type attrs: str 174 | @param is_empty: Is expanded element empty or not 175 | @type is_empty: bool 176 | @return: dict 177 | """ 178 | result = { 179 | 'name': tag_name, 180 | 'is_empty': is_empty 181 | }; 182 | 183 | if attrs: 184 | result['attributes'] = []; 185 | for m in re_attrs.findall(attrs): 186 | result['attributes'].append({ 187 | 'name': m[0], 188 | 'value': m[2] 189 | }) 190 | 191 | return Entry(TYPE_ABBREVIATION, key, result) 192 | 193 | def parse_abbreviation(key, value): 194 | """ 195 | Parses single abbreviation 196 | @param key: Abbreviation name 197 | @type key: str 198 | @param value: Abbreviation value 199 | @type value: str 200 | """ 201 | key = key.strip() 202 | if key[-1] == '+': 203 | # this is expando, leave 'value' as is 204 | return make_expando(key, value) 205 | else: 206 | m = re_tag.search(value) 207 | if m: 208 | return make_abbreviation(key, m.group(1), m.group(2), (m.group(4) == '/')) 209 | else: 210 | # assume it's reference to another abbreviation 211 | return Entry(TYPE_REFERENCE, key, value) 212 | 213 | def set_vocabulary(data, type): 214 | """ 215 | Sets new unparsed data for specified settings vocabulary 216 | @type data: object 217 | @param type: Vocabulary type ('system' or 'user') 218 | @type type: str 219 | """ 220 | if type == VOC_SYSTEM: 221 | vocabularies[VOC_SYSTEM] = data 222 | else: 223 | vocabularies[VOC_USER] = data 224 | 225 | def get_resource(syntax, name, item): 226 | """ 227 | Returns resource value from data set with respect of inheritance 228 | @param syntax: Resource syntax (html, css, ...) 229 | @type syntax: str 230 | @param name: Resource name ('snippets' or 'abbreviation') 231 | @type name: str 232 | @param abbr: Abbreviation name 233 | @type abbr: str name 234 | """ 235 | return get_parsed_item(VOC_USER, syntax, name, item) \ 236 | or get_parsed_item(VOC_SYSTEM, syntax, name, item) 237 | 238 | def get_abbreviation(syntax, name): 239 | """ 240 | Returns abbreviation value from data set 241 | @param syntax: Resource syntax (html, css, ...) 242 | @type syntax: str 243 | @param name: Abbreviation name 244 | @type name: str 245 | """ 246 | if name is None: 247 | return False 248 | 249 | return get_resource(syntax, 'abbreviations', name) \ 250 | or get_resource(syntax, 'abbreviations', name.replace('-', ':')) 251 | 252 | def get_snippet(syntax, name): 253 | """ 254 | Returns snippet value from data set 255 | @param syntax: Resource syntax (html, css, ...) 256 | @type syntax: str 257 | @param name: Snippet name 258 | @type name: str 259 | """ 260 | if name is None: 261 | return False 262 | 263 | return get_resource(syntax, 'snippets', name) \ 264 | or get_resource(syntax, 'snippets', name.replace('-', ':')) 265 | 266 | def get_variable(name): 267 | """ 268 | Returns variable value 269 | @param name: Variable name 270 | @type name: str 271 | """ 272 | return _get_subset(VOC_USER, 'variables', name) \ 273 | or _get_subset(VOC_SYSTEM, 'variables', name) 274 | 275 | def get_subset(syntax, name): 276 | """ 277 | Returns resource subset from settings vocabulary 278 | @param syntax: Syntax name 279 | @type syntax: str 280 | @param name: Resource name 281 | @type name: str 282 | """ 283 | return _get_subset(VOC_USER, syntax, name) \ 284 | or _get_subset(VOC_SYSTEM, syntax, name) 285 | 286 | def is_item_in_collection(syntax, collection, item): 287 | """ 288 | Check if specified item exists in specified resource collection 289 | (like 'empty', 'block_level') 290 | @param {String} syntax 291 | @param {String} collection Collection name 292 | @param {String} item Item name 293 | """ 294 | user_voc = get_vocabulary(VOC_USER) 295 | if syntax in user_voc and item in get_elements_collection(user_voc[syntax], collection): 296 | return True 297 | try: 298 | return item in get_elements_collection(get_vocabulary(VOC_SYSTEM)[syntax], collection) 299 | except: 300 | return False 301 | 302 | def get_elements_collection(resource, name): 303 | """ 304 | Returns specified elements collection (like 'empty', 'block_level') from 305 | resource. If collections wasn't found, returns empty object 306 | @type resource: object 307 | @type name: str 308 | """ 309 | if resource and has_deep_key(resource, ['element_types', name]): 310 | # if it's not parsed yet -- do it 311 | res = resource['element_types'] 312 | if not is_parsed(res[name]): 313 | res[name] = [el.strip() for el in res[name].split(',')] 314 | 315 | return res[name] 316 | else: 317 | return {} 318 | 319 | def has_syntax(syntax): 320 | """ 321 | Check if there are resources for specified syntax 322 | @type syntax: str 323 | @returns: bool 324 | """ 325 | return syntax in get_vocabulary(VOC_USER) or syntax in get_vocabulary(VOC_SYSTEM) 326 | 327 | class Entry: 328 | """ 329 | Unified object for parsed data 330 | """ 331 | def __init__(self, entry_type, key, value): 332 | """ 333 | @type entry_type: str 334 | @type key: str 335 | @type value: dict 336 | """ 337 | self.type = entry_type 338 | self.key = key 339 | self.value = value 340 | 341 | def __repr__(self): 342 | return 'Entry[type=%s, key=%s, value=%s]' % (self.type, self.key, self.value) 343 | 344 | # init vocabularies 345 | set_vocabulary(zen_settings, VOC_SYSTEM) 346 | user_settings = None 347 | 348 | # try to load settings from user's home folder 349 | fp = None 350 | 351 | try: 352 | fp, pathname, description = imp.find_module('my_zen_settings', [os.path.expanduser('~')]) 353 | module = imp.load_module('my_zen_settings', fp, pathname, description) 354 | user_settings = module.my_zen_settings 355 | except: 356 | pass 357 | finally: 358 | # Since we may exit via an exception, close fp explicitly. 359 | if fp: fp.close() 360 | 361 | if not user_settings: 362 | # try to load local module 363 | try: 364 | from my_zen_settings import my_zen_settings 365 | user_settings = my_zen_settings 366 | except: 367 | pass 368 | 369 | if user_settings: 370 | set_vocabulary(user_settings, VOC_USER) -------------------------------------------------------------------------------- /ZenCodingPython/zencoding/zen_editor.py: -------------------------------------------------------------------------------- 1 | ''' 2 | High-level editor interface that communicates with underlying editor (like 3 | Espresso, Coda, etc.) or browser. 4 | Basically, you should call set_context(obj) method to 5 | set up undelying editor context before using any other method. 6 | 7 | This interface is used by zen_actions.py for performing different 8 | actions like Expand abbreviation 9 | 10 | @example 11 | import zen_editor 12 | zen_editor.set_context(obj); 13 | //now you are ready to use editor object 14 | zen_editor.get_selection_range(); 15 | 16 | @author Sergey Chikuyonok (serge.che@gmail.com) 17 | @link http://chikuyonok.ru 18 | ''' 19 | 20 | import Npp 21 | import re 22 | import sys 23 | import zencoding 24 | import zencoding.utils 25 | 26 | class ScintillaStr(): 27 | """Class to emulate a string efficiently 28 | in Scintilla. Zen Coding uses get_content() a lot, 29 | when actually it only needs a small section of the 30 | content, so we emulate the slicing in this class. 31 | If a real string is requested, we return getText() 32 | In PythonScript 0.9.2.0 onwards, this could be 33 | getCharacterPointer() 34 | """ 35 | 36 | def __init__(self): 37 | self._editor = Npp.editor 38 | 39 | def __str__(self): 40 | return self._editor.getText() 41 | 42 | def __repr__(self): 43 | return self._editor.getText() 44 | 45 | def __getitem__(self, i): 46 | if i.__class__.__name__ == 'slice': 47 | stop = i.stop 48 | if stop == sys.maxint: 49 | stop = -1 50 | return self._editor.getTextRange(i.start, stop) 51 | else: 52 | ch = self._editor.getCharAt(i) 53 | return ch >= 0 and chr(ch) or chr(ch + 256) 54 | 55 | def __setitem__(self, i, c): 56 | if i.__class__.__name__ == 'slice': 57 | start, stop = (i.start, i.stop) 58 | else: 59 | start, stop = i, i 60 | if stop > self._editor.getLength(): 61 | stop = self._editor.getLength() 62 | 63 | self._editor.setTarget(start, stop) 64 | self._editor.replaceTarget(c) 65 | 66 | 67 | def __len__(self): 68 | return self._editor.getLength() 69 | 70 | def find(self, search, start=0): 71 | found = self._editor.findText(0, start, self._editor.getLength(), search) 72 | if found: 73 | return found[0] 74 | else: 75 | return -1 76 | 77 | class ZenEditor(): 78 | def __init__(self): 79 | 80 | self._editor = Npp.editor 81 | self._notepad = Npp.notepad 82 | self.padding_re = re.compile(r"^(\s+)") 83 | self.syntax_map = { Npp.LANGTYPE.HTML : 'html', 84 | Npp.LANGTYPE.CSS : 'css', 85 | Npp.LANGTYPE.XML : 'xml' 86 | } 87 | 88 | def set_context(self, context): 89 | """ 90 | Setup underlying editor context. You should call this method 91 | before using any Zen Coding action. 92 | @param context: context object 93 | """ 94 | self._context = context 95 | 96 | 97 | def get_selection_range(self): 98 | """ 99 | Returns character indexes of selected text 100 | @return: list of start and end indexes 101 | @example 102 | start, end = zen_editor.get_selection_range(); 103 | print('%s, %s' % (start, end)) 104 | """ 105 | return self._editor.getSelectionStart(), self._editor.getSelectionEnd() 106 | 107 | 108 | def create_selection(self, start, end=None): 109 | """ 110 | Creates selection from start to end character 111 | indexes. If end is ommited, this method should place caret 112 | and start index 113 | @type start: int 114 | @type end: int 115 | @example 116 | zen_editor.create_selection(10, 40) 117 | # move caret to 15th character 118 | zen_editor.create_selection(15) 119 | """ 120 | if end is None: 121 | end = start 122 | self._editor.setSelection(start, end) 123 | 124 | def get_current_line_range(self): 125 | """ 126 | Returns current line's start and end indexes 127 | @return: list of start and end indexes 128 | @example 129 | start, end = zen_editor.get_current_line_range(); 130 | print('%s, %s' % (start, end)) 131 | """ 132 | start = self._editor.lineFromPosition(self._editor.getSelectionStart()) 133 | end = self._editor.lineFromPosition(self._editor.getSelectionEnd()) 134 | return start,end 135 | 136 | def get_line_from_position(self, index): 137 | """ 138 | Returns the line contents from the file offset 139 | provided by index 140 | @return: string of the line 141 | @example 142 | linecontent = zen_editor.get_line_from_position(32) 143 | print(linecontent) 144 | """ 145 | return self._editor.getLine(self._editor.lineFromPosition(index)) 146 | 147 | def char_at(self, index): 148 | ch = self._editor.getCharAt(index) 149 | return ch >= 0 and chr(ch) or chr(ch + 256) 150 | 151 | def get_caret_pos(self): 152 | """ Returns current caret position """ 153 | return self._editor.getCurrentPos() 154 | 155 | def set_caret_pos(self, pos): 156 | """ 157 | Set new caret position 158 | @type pos: int 159 | """ 160 | self._editor.gotoPos(pos) 161 | 162 | def get_current_line(self): 163 | """ 164 | Returns content of current line 165 | @return: str 166 | """ 167 | return self._editor.getCurLine() 168 | 169 | def replace_content(self, value, start=None, end=None, no_indent=False, undo_name='Replace content'): 170 | """ 171 | Replace editor's content or it's part (from start to 172 | end index). If value contains 173 | caret_placeholder, the editor will put caret into 174 | this position. If you skip start and end 175 | arguments, the whole target's content will be replaced with 176 | value. 177 | 178 | If you pass start argument only, 179 | the value will be placed at start string 180 | index of current content. 181 | 182 | If you pass start and end arguments, 183 | the corresponding substring of current target's content will be 184 | replaced with value 185 | @param value: Content you want to paste 186 | @type value: str 187 | @param start: Start index of editor's content 188 | @type start: int 189 | @param end: End index of editor's content 190 | @type end: int 191 | """ 192 | 193 | caret_placeholder = zencoding.utils.get_caret_placeholder() 194 | realStart = start 195 | caret_pos = -1 196 | if realStart is None: 197 | realStart = 0 198 | 199 | line_padding = self.padding_re.search(self._editor.getCurLine()) 200 | if line_padding and not no_indent: 201 | line_padding = line_padding.group(1) 202 | value = zencoding.utils.pad_string(value, line_padding) 203 | 204 | 205 | new_pos = value.find(caret_placeholder) 206 | if new_pos != -1: 207 | caret_pos = realStart + new_pos 208 | value = value.replace(caret_placeholder, '') 209 | else: 210 | caret_pos = realStart + len(value) 211 | 212 | 213 | 214 | if start is None: 215 | self._editor.setText(value) 216 | else: 217 | if end is None: 218 | self._editor.insertText(start, value) 219 | else: 220 | self._editor.setTarget(start, end) 221 | self._editor.replaceTarget(value) 222 | 223 | if caret_pos != -1: 224 | self._editor.gotoPos(caret_pos) 225 | 226 | 227 | 228 | def get_content(self): 229 | """ 230 | Returns editor's content 231 | @return: str 232 | """ 233 | return ScintillaStr() 234 | 235 | def get_syntax(self): 236 | """ 237 | Returns current editor's syntax mode 238 | @return: str 239 | """ 240 | 241 | syntax = self.syntax_map.get(self._notepad.getLangType(), 'html') 242 | 243 | # Same language used for XML/XSL/XSD 244 | if syntax == 'xml': 245 | if self._notepad.getCurrentFilename()[-4:].lower() == '.xsl': 246 | syntax = 'xsl' 247 | elif self._notepad.getCurrentFilename()[-4:].lower() == '.xsd': 248 | syntax = 'xsd' 249 | 250 | return syntax 251 | 252 | def get_profile_name(self): 253 | """ 254 | Returns current output profile name (@see zen_coding#setup_profile) 255 | @return {String} 256 | """ 257 | return 'xhtml' 258 | 259 | def prompt(self, title): 260 | """ 261 | Ask user to enter something 262 | @param title: Dialog title 263 | @type title: str 264 | @return: Entered data 265 | @since: 0.65 266 | """ 267 | return self._editor.prompt(title, "Zen Coding - Python") 268 | 269 | 270 | def get_selection(self): 271 | """ 272 | Returns current selection 273 | @return: str 274 | @since: 0.65 275 | """ 276 | return self._editor.getSelText() 277 | 278 | def get_file_path(self): 279 | """ 280 | Returns current editor's file path 281 | @return: str 282 | @since: 0.65 283 | """ 284 | return self._notepad.getCurrentFilename() 285 | 286 | def add_placeholders(self, text): 287 | _ix = [1000] 288 | 289 | def get_ix(m): 290 | _ix[0] += 1 291 | return '${%s}' % _ix[0] 292 | 293 | return re.sub(zencoding.utils.get_caret_placeholder(), get_ix, text) 294 | -------------------------------------------------------------------------------- /dllmain.cpp: -------------------------------------------------------------------------------- 1 | // dllmain.cpp : Defines the entry point for the DLL application. 2 | #include "stdafx.h" 3 | 4 | BOOL APIENTRY DllMain( HMODULE hModule, 5 | DWORD ul_reason_for_call, 6 | LPVOID lpReserved 7 | ) 8 | { 9 | switch (ul_reason_for_call) 10 | { 11 | case DLL_PROCESS_ATTACH: 12 | case DLL_THREAD_ATTACH: 13 | case DLL_THREAD_DETACH: 14 | case DLL_PROCESS_DETACH: 15 | break; 16 | } 17 | return TRUE; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /resource.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruderstein/ZenCoding-Python/d291792c6ab154e6ffe41781eab222a0eaeccbaf/resource.h -------------------------------------------------------------------------------- /stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // ZenCoding-Python.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 11 | // Windows Header Files: 12 | #include 13 | 14 | 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | typedef std::basic_string tstring; -------------------------------------------------------------------------------- /targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | --------------------------------------------------------------------------------