diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dcc40d8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +docs/ export-ignore +resources/*.png export-ignore +resources/*.py export-ignore \ No newline at end of file diff --git a/.gitignore b/.gitignore index 418af2e..bee8a64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -Thumbs.db -__pycache__/ -cache.txt -venv/ -site/ +__pycache__ diff --git a/.sublime/Default.sublime-keymap b/.sublime/Default.sublime-keymap deleted file mode 100644 index 8808ee0..0000000 --- a/.sublime/Default.sublime-keymap +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "keys": ["alt+m"], - "command": "new_markdown_live_preview", - "context": [ - { - "key": "selector", - "operator": "equal", - "operand": "text.html.markdown" - } - ] - } -] diff --git a/.sublime/Main.sublime-menu b/.sublime/Main.sublime-menu deleted file mode 100644 index 2af2e98..0000000 --- a/.sublime/Main.sublime-menu +++ /dev/null @@ -1,33 +0,0 @@ -[ - { - "id": "preferences", - "children": [ - { - "id": "package-settings", - "children": [ - { - "caption": "MarkdownLivePreview", - "children": [ - { - "caption": "Settings", - "command": "edit_settings", - "args": { - "base_file": "$packages/MarkdownLivePreview/.sublime/MarkdownLivePreview.sublime-settings", - "default": "// Your settings for MarkdownLivePreview. See the default file to see the different options. \n{\n\t\n}\n" - } - }, - { - "caption": "Style - CSS", - "command": "open_file", - "args": { - "file": "$packages/User/MarkdownLivePreview.css", - "contents": "/* See http://www.sublimetext.com/docs/3/minihtml.html#css to know which property you're able to use */\n\n$0" - } - } - ] - } - ] - } - ] - } -] diff --git a/.sublime/MarkdownLivePreview.sublime-commands b/.sublime/MarkdownLivePreview.sublime-commands deleted file mode 100644 index 01ab39c..0000000 --- a/.sublime/MarkdownLivePreview.sublime-commands +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "caption": "MarkdownLivePreview: Edit Current File", - "command": "new_markdown_live_preview" - }, - { - "caption": "MarkdownLivePreview: Clear the cache", - "command": "markdown_live_preview_clear_cache" - }, - { - "caption": "MarkdownLivePreview: Edit Custom CSS File", - "command": "open_file", - "args": { - "file": "$packages/User/MarkdownLivePreview.css" - } - }, - { - "caption": "Preferences: MarkdownLivePreview Settings", - "command": "edit_settings", - "args": { - "base_file": "${packages}/MarkdownLivePreview/.sublime/MarkdownLivePreview.sublime-settings", - "default": "// Your settings for MarkdownLivePreview. See the default file to see the different options. \n{\n\t$0\n}\n" - } - } -] diff --git a/.sublime/MarkdownLivePreview.sublime-settings b/.sublime/MarkdownLivePreview.sublime-settings deleted file mode 100644 index b3f67a3..0000000 --- a/.sublime/MarkdownLivePreview.sublime-settings +++ /dev/null @@ -1,19 +0,0 @@ -{ - // As soon as you open a markdown file, it opens the window preview - "markdown_live_preview_on_open": false, - - // If an image starts with one of those strings, then it will be loaded from internet - "load_from_internet_when_starts": ["http://", "https://"], - - // When the preview is opened, the markdown file is closed in the origin window and reopend in - // the preview window. If this option is set to 'true', then the markdown file will NOT be - // closed in the origin window - "keep_open_when_opening_preview": false, - - // Choose what to do with YAML/TOML (---/+++ respectively) headers - // Valid values: "wrap_in_pre", "remove". - "header_action": "wrap_in_pre", - - // Wait at least the specified *seconds* before updating the preview. - "update_preview_every": 0 -} diff --git a/.sublime/MarkdownLivePreviewSyntax.hidden-tmLanguage b/.sublime/MarkdownLivePreviewSyntax.hidden-tmLanguage deleted file mode 100644 index af0511f..0000000 --- a/.sublime/MarkdownLivePreviewSyntax.hidden-tmLanguage +++ /dev/null @@ -1,14 +0,0 @@ - - - - - name - MarkdownLivePreviewSyntax - - patterns - - - scopeName - text.markdown-live-preview - - diff --git a/404.txt b/404.txt deleted file mode 100644 index 77afa64..0000000 --- a/404.txt +++ /dev/null @@ -1 +0,0 @@ -data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAArCAYAAACO7C3tAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACHDgAAjBIAAPxoAACFkgAAeeQAAO3tAAA7IgAAIN+/WeXLAAAKqGlDQ1BJQ0MgUHJvZmlsZQAASMetl2dQFOkWhk/35EQaGAEJQ06C5Cg5DqAgGUwMM4QhjOPAkEyoLCq4oqiIgAldJCi4BkDWgIhiWhQT5gVZVNTrYkBUVG4Dl+Huj/1xq+6pOtVPnf76/U73nK/qHQD6Za5IlIrKAaQJM8Qhvh7sqOgYNukpEEEDlMAO9Lm8dJF7cHAg/GN8ugfI+PW26bgW/G8hz49P5wEgwRjH8dN5aRifwLKBJxJnAOD4WF0nK0M0zhswVhRjDWJcOc6Jk3x0nOMmuWNiTViIJ8b3Ach0LlecCED7E6uzM3mJmA4dj7G5kC8QYmyNsQsviYvtQ8fuway0tKXjvA9jw7j/0kn8m2acVJPLTZTy5LtMBNlLkC5K5ebA/zvSUiVTe2hhSU8S+4VgVwXsm1WmLA2QsjBuXtAUC/gT6yc4SeIXPsW8dM+YKeZzvQKmWJIS7j7FXPH0s4IMTtgUi5eGSPWFqfMCpfrxHCnHp3uHTnGCwIczxblJYZFTnCmImDfF6SmhAdNrPKV1sSRE2nOC2Ef6jmnp073xuNN7ZSSF+UnfK97LW9qPMFy6RpThIdURpQZP95zqK62nZ4ZKn83AhmqKk7n+wdM6wdJvAp4gACHEQxpwgQ1+4AWQEZ89PlfguVSUIxYkJmWw3bFTEs/mCHlms9iW5hbYBI6fucmf9MP9ibOEsMjTtdwUALfLAKj3dC0Sm926WgCW2nRN9xs2+sUArdd4EnHmZG181IEAVJAFRVDBzrQOGIIpWIItOIEbeIM/BEEYRMNi4EES1rcYsmAFrIECKIItsAPKYS8cgBo4AsegGU7DebgE1+Am3IVH0AsD8BqG4BOMIghCQhgIE1FBNBE9xASxROwRF8QbCURCkGgkFklEhIgEWYGsQ4qQEqQc2Y/UIr8ip5DzyBWkG3mA9CGDyHvkK4pD6agiqo7qo7NRe9QdDUDD0EVoIroMzUXz0c1oGVqFHkab0PPoNfQu2ou+RodxgKPhWDgtnCnOHueJC8LF4BJwYtwqXCGuFFeFa8C14jpxt3G9uDe4L3ginoln403xTng/fDieh1+GX4XfhC/H1+Cb8B342/g+/BD+B4FBUCOYEBwJHEIUIZGQRSgglBKqCScJFwl3CQOET0QikUU0INoR/YjRxGTicuIm4m5iI7GN2E3sJw6TSCQVkgnJmRRE4pIySAWkXaTDpHOkW6QB0mcyjaxJtiT7kGPIQvJacim5jnyWfIv8gjxKkaPoURwpQRQ+JYdSTDlIaaXcoAxQRqnyVAOqMzWMmkxdQy2jNlAvUh9TP9BoNG2aA20+TUDLo5XRjtIu0/poX+gKdGO6J30hXULfTD9Eb6M/oH9gMBj6DDdGDCODsZlRy7jAeMr4LMOUMZPhyPBlVstUyDTJ3JJ5K0uR1ZN1l10smytbKntc9obsGzmKnL6cpxxXbpVchdwpuR65YXmmvIV8kHya/Cb5Ovkr8i8VSAr6Ct4KfIV8hQMKFxT6mTimDtOTyWOuYx5kXmQOKBIVDRQ5ismKRYpHFLsUh5QUlKyVIpSylSqUzij1snAsfRaHlcoqZh1j3WN9naE+w31G/IyNMxpm3JoxojxT2U05XrlQuVH5rvJXFbaKt0qKylaVZpUnqnhVY9X5qlmqe1Qvqr6ZqTjTaSZvZuHMYzMfqqFqxmohasvVDqhdVxtW11D3VRep71K/oP5Gg6XhppGssV3jrMagJlPTRVOguV3znOYrthLbnZ3KLmN3sIe01LT8tCRa+7W6tEa1DbTDtddqN2o/0aHq2Osk6GzXadcZ0tXUnau7Qrde96EeRc9eL0lvp16n3oi+gX6k/nr9Zv2XBsoGHINcg3qDx4YMQ1fDZYZVhneMiEb2RilGu41uGqPGNsZJxhXGN0xQE1sTgcluk+5ZhFkOs4Szqmb1mNJN3U0zTetN+8xYZoFma82azd7O1p0dM3vr7M7ZP8xtzFPND5o/slCw8LdYa9Fq8d7S2JJnWWF5x4ph5WO12qrF6p21iXW89R7r+zZMm7k2623abb7b2tmKbRtsB+107WLtKu167BXtg+032V92IDh4OKx2OO3wxdHWMcPxmONfTqZOKU51Ti/nGMyJn3NwTr+ztjPXeb9zrwvbJdZln0uvq5Yr17XK9ZmbjhvfrdrthbuRe7L7Yfe3HuYeYo+THiOejp4rPdu8cF6+XoVeXd4K3uHe5d5PfbR9En3qfYZ8bXyX+7b5EfwC/Lb69XDUOTxOLWfI385/pX9HAD0gNKA84FmgcaA4sHUuOtd/7ra5j+fpzRPOaw6CIE7QtqAnwQbBy4J/m0+cHzy/Yv7zEIuQFSGdoczQJaF1oZ/CPMKKwx6FG4ZLwtsjZCMWRtRGjER6RZZE9kbNjloZdS1aNVoQ3RJDiomIqY4ZXuC9YMeCgYU2CwsW3ltksCh70ZXFqotTF59ZIruEu+R4LCE2MrYu9hs3iFvFHY7jxFXGDfE8eTt5r/lu/O38wXjn+JL4FwnOCSUJLxOdE7clDia5JpUmvRF4CsoF75L9kvcmj6QEpRxKGUuNTG1MI6fFpp0SKghThB1LNZZmL+0WmYgKRL3LHJftWDYkDhBXpyPpi9JbMhQxc3NdYij5SdKX6ZJZkfk5KyLreLZ8tjD7eo5xzsacF7k+ub8sxy/nLW9fobVizYq+le4r969CVsWtal+tszp/9UCeb17NGuqalDW/rzVfW7L247rIda356vl5+f0/+f5UXyBTIC7oWe+0fu8G/AbBhq6NVht3bfxRyC+8WmReVFr0bRNv09WfLX4u+3lsc8LmrmLb4j1biFuEW+5tdd1aUyJfklvSv23utqbt7O2F2z/uWLLjSql16d6d1J2Snb1lgWUtu3R3bdn1rTyp/G6FR0VjpVrlxsqR3fzdt/a47WnYq763aO/XfYJ99/f77m+q0q8qPUA8kHng+cGIg52/2P9SW61aXVT9/ZDwUG9NSE1HrV1tbZ1aXXE9Wi+pHzy88PDNI15HWhpMG/Y3shqLjsJRydFXv8b+eu9YwLH24/bHG07onag8yTxZ2IQ05TQNNSc197ZEt3Sf8j/V3urUevI3s98OndY6XXFG6UzxWerZ/LNj53LPDbeJ2t6cTzzf376k/dGFqAt3OuZ3dF0MuHj5ks+lC53unecuO18+fcXxyqmr9lebr9lea7puc/3k7za/n+yy7Wq6YXej5abDzdbuOd1nb7neOn/b6/alO5w71+7Ou9t9L/ze/Z6FPb33+fdfPkh98O5h5sPRR3mPCY8Ln8g9KX2q9rTqD6M/Gntte8/0efVdfxb67FE/r//1n+l/fhvIf854XvpC80XtS8uXpwd9Bm++WvBq4LXo9eibgn/J/6vyreHbE3+5/XV9KGpo4J343dj7TR9UPhz6aP2xfTh4+OmntE+jI4WfVT7XfLH/0vk18uuL0axvpG9l342+t/4I+PF4LG1sTMQVcyesAA5LNCEB4P0hAEY0APMmAFVm0hNPBDLp4ycI/oknffNE2AIcaMO8CJb+WFbmAehhycRuBbsBhLkBamUlzf9EeoKV5aQWrRmzJqVjYx8wL0gyAvjeMzY22jw29r0aa/YhQNunSS8+HkTsH0qJFoqYMS9kj+T93RED/BtQMACLL7YRYgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAC/RJREFUeF7Vm3WoVU8Qx/c9u7sQC8VCBcUGUexEQUVFRRED7MbuVkxsBfUPG1swUcQCu7u7u+v3+6xn32/v3nPvO/X0/b6w+s6ePTu7M7Mzs7N7Y379C/E/w9u3b8W7d+/Ely9f5N/Pnz+X9VmyZBEZM2YUyZMnF+nSpRPp06eX9f8n+BLIqVOnZEmSJIlV8xt0SV2TJk0kY4LAt2/fxJEjR8SxY8fE2bNnxYMHD6QwoBUbGyvb/Pz5U8TExEiauXPnFiVLlhTlypUTlSpVEilSpJBt/gS2bt0qlUSNS+H79+9yXHXr1rVqwuFZIK9fvxY9evQQ165dE0mTJrVqf+P9+/eiUaNGYuzYsVaNP5w4cUJMmDBB0vz8+bNIliyZFDjMp+hgOpQfP35IIaZMmVKulEGDBokKFSpYrRIOhw4dEkOGDJG0dYEwJlb0lClTRPXq1a3acHgWyKRJk8SGDRvCzAJagMnYtWuXVeMdaNnMmTPFzp07RerUqaXg7YQQCbpwPnz4IGrWrCl69eolcubMabUIFtDo2LGjXL3wQAfvatSoIcaNG2fV2CN0TTkEBDdu3ChNg2KQYhJa0L17d/m3X/Ts2VPs3btXZMiQQU4QjTOFoZiuig7a8g0rij72798vevfuHdYuKKxcuVLcvHlTjlXnCwqB4jrhiyeBjBo1Ko5BCkwSYVSpUkU0aNDAqvUGzFKXLl3E7du3RZo0aWyFwCRp9+nTJ/H161dZ+Js63tkJh77u3r0rOnXqJDU2SDDWFStWiLRp04aMl3FgNaDpZGW6NlmrV68W8+bNi9MCBZiAgBYuXCgKFixo1XoDAsfkYaZMYeC4P378KDJnzizKli0rsmbNKjJlyiTbvXz5UhYcP+YOAehKA5gu32PH8XFm/17AmPr37y/pEjyoPqGFopQqVUqaXtPX2sGVQN68eSPatWsnnStmQAfvWrZsKfr27WvVeMOZM2dE165dpTM2VyCaBjM7d+4sateuLfLmzWu9DcX9+/elqZs7d26I71GAgaykWbNmSaH6xZ49e8SYMWMkT8wxP3v2TKxZs0YULVrUqo0OVwLBIaIFpuaiBSxHCPtFvXr1pOnRBc4QiVrwAzNmzHC8AjEj+CGUhf70MdMfgoKZfkBESXiPhVCRH2DMmMVmzZqJfv36yToncOxDTp48KUM6Uxgq9sfm+8WWLVvEixcvwpY2NFgxw4cPd2UO8+fPL7/hW/rQAQ2YuXbtWqvGG+bMmSMZrwsDIPA8efKIDh06WDXO4EggmAn8BjZZB1qAI69YsWLU2Nop8BswTwc0WIHNmzeXmzy34JumTZvKPkxjgB/ct2+f9eQep0+flivMTklB69atpX9zA0cCWb58ubhw4ULYsleay6bLL/AdhIymvVchI/G9V+BzUCZ9lUADWrdu3ZIbT7dASWfPni0FbQYOKCmK0LhxY6vGOeIVCA5y/fr1IlWqVCGMQtuwzW3atJH5I79A2wgWzMnhT9Bwv2jYsKF05DqgRfqF9I9bYF6vXLkSlpJB6AhrwIABVo07xCsQQlAkbkYPaEaZMmWkQPyC6AmmmKuDyUEXp+kXCBU6utlSqwTazMcpSGwSxuohLqBvhIE/zZEjh1XrDlEFQpLs4sWL0iyZjGIwmAIm5BdoLubKTFLCwNKlSweStSUTXLx4cdmnDsYPbTcCGT16tByrPl6lpIS3fpQ0okDYYK1atUo6Ph0QhoGVK1f25GTtgFY9evQoTCBEKiVKlAjb83gBCkRf9KmDFcheAfPrBKRfjh8/bht8oLStWrUKC37cIKJA5s+fH9HJMjm0JCjg0KFhLn+e8+XLZ9X4B32ZaRVoIBQnfoSQHL6osenA15E2YsPqB7YCIaLatm2blLTJJAiz2QoS9+7dC1sd0CIvhKkJCqRZ0GxdIAClYwzxYdmyZeLOnTth0aZSUjcbwEiw3am3bdtWEoaIAs0wVdj06dOnh5gRbOfixYvl2Yhej71mcxRfOoXwkRyZvtSZJDvzyZMni8KFC1u1/sAqGDZsmFQqXQEwmYSoAwcOtGrC8eTJExmp4c/MAAcnz9mQ6TvIFMAX+KZ/Az3acnBmImyFLFmyRMbmpu/AkZNuJ4Vs2nTenT9/Xp7oHT16NK7wzOlefLALd5kodAi3gwKrwxw7gDbhbyQwPw6dGIspDJSRg68WLVpYtf8BQZFq0nlCIeOBgO0QwgX2HMTXDNq0kaQH6tSpI4oUKWLV/Afa8g0rikmrwrMdA0yQwjDpMVlMiZPvnQIlY2XQtw5oM4ZI2Lx5s7h+/XrYWOgHYbVv3952nAgPHtjxxTTRCiECIbYmurKLdliqHO4kBOwEAhCIuVL9QAnEBLTRZjuQxl+6dKlso48RYWB6OB9nPxYU4gRy8OBBsXv3bilBnTAaoJZsQgFTYGotwAeZ+wY/oC/mYgLazNsOU6dOjUt46nyhr2zZsgXiyHVIgeB0SB6qgx4FBsq78uXLi2rVqlm1wQPfZAqEceDY3WzY4gMZB/rU5wigbXc7hnD8wIEDtslDBMLZUJA+DkiBIAyiKrRAB4PPnj27jD7MSejAJppOWSFSvQ7MYaQVAhODAqbX3BgCGGzm49gosjowc6aS0kfVqlXjzbFF838mrxViL1++LLZv3y6ZajKdgRYqVEi8evVKRgt2hUzp4cOHpQ02mc8zdvbSpUtx7Ykybty4YbX4DcJduxXC6owW/bgFTKZPc57QZhXoWLdunYw2YarZnmfufJ07dy6EF3ohxCbChIfm9wgDHvBetYeHjC/m3wjiF1dTiPlN0BmCYsB2GgwUMbSZNjpx9Q1aRl8Ajec6ztChQ+Uz2LRpk5g4cWJIzopvaTtixAhRq1Ytq9YfiCChY254UaY+ffrEha5YBgIYMtCmb1G8wFTRTs3RBP3zDr6oZwV4gVBQWPU9Y2B/F0tFNHMDU7DjarmbhXcU+tGJAvVMH6o9f6tBKrDZVAJV4Fvac+UoKHDjhH71cUITOnqkpN7r7RTU92wu4+ML/6tvdMBX5qt/D1/kOzuiOvjYSYnUD/XxteUGCUWtIgXMBSbVrPcCGHD16tUw241AcOhmujwaX+zmFKlE6seurayX//5lYNI4K8cE6EAgZFaDuEOFSeBegOloEVSBAgUC3e/4gTRZFLTQT9HNjQ6zb7t2+CnOKli6+ns2cTg6Ug1+wdk5JkJpIoAWNEnLR/IV+tjdFtWHHcy+VbtYBkTkgU30WrB/eqcKPKP1Zv8wxgR+hEjH7AMHTJbVL7jmaUZS0EIZzJ02c2GM+pi9lGh8seM7q9XzZWsddEFGF5OgL30mVaxYMbFgwQKrJjIYXLdu3WTOSO+Dvnk3cuRIz9HWjh075CVnMwvB+DBXXKjTM81BAZ/FjUZSQ3rKBvPJfLiDZiIQH4K0I4WAaIgTwCxuEcIkvR8YiCNetGiRePjwoVXrHJxEksGmD10Y0GDcrMyEEAYwI0cF6kx/qRCIQOyIKkR7Z4ILyQzUFCLMfPz4sRg/frxV4xx8Q6rbLrpC+EFc8IsEL3xJFFGWAkzjpiH2VB8wmo2tZ6PGMsc0qrjdDjCaPBS38GlrZiHomwzC4MGDIyYV/xYSlUAAZ9JEPQhFBwzFKVNPhhXbjKMmeiIFQeECAhczuBOFTyNcNhODgD4416lfv75Vk3gQiFNHW2ECGow2KqCpHL9yjOkG5HlgOmczen8KmDTsP2BfocwRNluvRxCmMBgraaJp06bZHrYFCe4mcKtT3f1VID/HUTI/+zOR6FYIYJPIOTvRFgw0dYa9BIJS0RiCoADqeEcb00zRF8LjIC6hheEViVIggN9+8ENPzmgIE+2iOBiu0g6qmCuCb/iW0JPkJeGv3x8UJSQSrUAAB2OcZ3MLBmD7MYNOQmna0Fb5In5MRLaXC36JGYEIhMmjgZybqJ+VUXhGu/2Cmy6YMK73s9HET3BTBZpES2wcKfxNHe8QBtc6uUnIj3xIp7OC/iTUOO34EilKDGynzqYNbdQnjaCw59zNCgrQYLPHxbanT5/K824KY+AiHBfrOOvG5OXKlSvwI1Y3gOkcHzA23ZTi7/jFmX7+8xtC/AOF/82HDx1fAgAAAABJRU5ErkJggg== \ No newline at end of file diff --git a/MLPApi.py b/MLPApi.py deleted file mode 100644 index b002615..0000000 --- a/MLPApi.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- encoding: utf-8 -*- - -import sublime -import sublime_plugin - -import os.path -from html.parser import HTMLParser - -from .lib import markdown2 as md2 -from .lib.pre_tables import pre_tables - -from .escape_amp import * -from .functions import * -from .setting_names import * -from .image_manager import CACHE_FILE -from random import randint as rnd - -__folder__ = os.path.dirname(__file__) - - -# used to store the phantom's set -windows_phantom_set = {} - - -def create_preview(window, file_name): - preview = window.new_file() - - preview.set_name(get_preview_name(file_name)) - preview.set_scratch(True) - preview.set_syntax_file('Packages/MarkdownLivePreview/.sublime/' + \ - 'MarkdownLivePreviewSyntax.hidden-tmLanguage') - - return preview - -def markdown2html(md, basepath, color_scheme): - - # removes/format the YAML/TOML header. - md = manage_header(md, get_settings().get('header_action')) - - html = '\n'.format(get_style(color_scheme)) - - - # the option no-code-highlighting does not exists in the official version of markdown2 for now - # I personaly edited the file (markdown2.py:1743) - html += md2.markdown(md, extras=['fenced-code-blocks', 'tables', 'strike']) - - # tables aren't supported by the Phantoms - # This function transforms them into aligned ASCII tables and displays them in a
 block
-    # (the ironic thing is that they aren't supported either :D)
-    html = pre_tables(html)
-
-    # pre block are not supported by the Phantoms.
-    # This functions replaces the \n in them with 
so that it does (1/2) - html = pre_with_br(html) - - # comments aren't supported by the Phantoms - # Simply removes them using bs4, so you can be sadic and type ``, these one - # won't be stripped! - html = strip_html_comments(html) - - # exception, again, because
 aren't supported by the phantoms
-    # so, because this is monosaped font, I just replace it with a '.' and make transparent ;)
-    html = html.replace(' ', '.')
-
-    # Phantoms have problem with images size when they're loaded from an url/path
-    # So, the solution is to convert them to base64
-    html = replace_img_src_base64(html, basepath=basepath)
-
-    # BeautifulSoup uses the 
but the sublime phantoms do not support them... - html = html.replace('
', '
').replace('
', '
') - - return html - -def show_html(md_view, preview): - global windows_phantom_set - html = markdown2html(get_view_content(md_view), os.path.dirname(md_view.file_name()), md_view.settings().get('color_scheme')) - - phantom_set = windows_phantom_set.setdefault(preview.window().id(), - sublime.PhantomSet(preview, 'markdown_live_preview')) - phantom_set.update([sublime.Phantom(sublime.Region(0), html, sublime.LAYOUT_BLOCK, - lambda href: sublime.run_command('open_url', {'url': href}))]) - - # lambda href: sublime.run_command('open_url', {'url': href}) - # get the "ratio" of the markdown view's position. - # 0 < y < 1 - y = md_view.text_to_layout(md_view.sel()[0].begin())[1] / md_view.layout_extent()[1] - # set the vector (position) for the preview - vector = [0, y * preview.layout_extent()[1]] - # remove half of the viewport_extent.y to center it on the screen (verticaly) - vector[1] -= preview.viewport_extent()[1] / 2 - # make sure the minimum is 0 - vector[1] = 0 if vector[1] < 0 else vector[1] - # the hide the first line - vector[1] += preview.line_height() - preview.set_viewport_position(vector, animate=False) - -def clear_cache(): - """Removes the cache file""" - os.remove(CACHE_FILE) - -def release_phantoms_set(view_id=None): - global windows_phantom_set - if view_id is None: - windows_phantom_set = {} - else: - del windows_phantom_set[view_id] diff --git a/MarkdownLivePreview.py b/MarkdownLivePreview.py index 9dd992a..3b09249 100644 --- a/MarkdownLivePreview.py +++ b/MarkdownLivePreview.py @@ -1,124 +1,223 @@ -# -*- encoding: utf-8 -*- - +import os.path import sublime import sublime_plugin -import time -from .MLPApi import * -from .setting_names import * -from .functions import * +from functools import partial -class NewMarkdownLivePreviewCommand(sublime_plugin.ApplicationCommand): +from .markdown2html import markdown2html +from .utils import * - def run(self): +MARKDOWN_VIEW_INFOS = "markdown_view_infos" +PREVIEW_VIEW_INFOS = "preview_view_infos" +# FIXME: put this as a setting for the user to choose? +DELAY = 100 # ms - """Inspired by the edit_settings command""" +def get_resource(resource): + path = 'Packages/MarkdownLivePreview/resources/' + resource + abs_path = os.path.join(sublime.packages_path(), '..', path) + if os.path.isfile(abs_path): + with open(abs_path, 'r') as fp: + return fp.read() + return sublime.load_resource(path) - current_view = sublime.active_window().active_view() - file_name = current_view.file_name() - if get_settings().get('keep_open_when_opening_preview') is False: - current_view.close() - if file_name is None: - return sublime.error_message('MarkdownLivePreview: Not supporting ' - 'unsaved file for now') +resources = {} + +def plugin_loaded(): + resources["base64_loading_image"] = get_resource('loading.base64') + resources["base64_404_image"] = get_resource('404.base64') + resources["stylesheet"] = get_resource('stylesheet.css') + +# try to reload the resources if we save this file +try: + plugin_loaded() +except OSError: + pass + +# Terminology +# original_view: the view in the regular editor, without it's own window +# markdown_view: the markdown view, in the special window +# preview_view: the preview view, in the special window +# original_window: the regular window +# preview_window: the window with the markdown file and the preview + +class MdlpInsertCommand(sublime_plugin.TextCommand): + + def run(self, edit, point, string): + self.view.insert(edit, point, string) + +class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand): + + def run(self, edit): + + """ If the file is saved exists on disk, we close it, and reopen it in a new + window. Otherwise, we copy the content, erase it all (to close the file without + a dialog) and re-insert it into a new view into a new window """ + + original_view = self.view + original_window_id = original_view.window().id() + file_name = original_view.file_name() + + syntax_file = original_view.settings().get('syntax') + + if file_name: + original_view.close() + else: + # the file isn't saved, we need to restore the content manually + total_region = sublime.Region(0, original_view.size()) + content = original_view.substr(total_region) + original_view.erase(edit, total_region) + original_view.close() + # FIXME: save the document to a temporary file, so that if we crash, + # the user doesn't lose what he wrote sublime.run_command('new_window') - self.window = sublime.active_window() - self.window.settings().set(PREVIEW_WINDOW, True) - self.window.run_command('set_layout', { + preview_window = sublime.active_window() + + preview_window.run_command('set_layout', { 'cols': [0.0, 0.5, 1.0], 'rows': [0.0, 1.0], 'cells': [[0, 0, 1, 1], [1, 0, 2, 1]] }) - self.window.focus_group(1) - preview = create_preview(self.window, current_view) - self.window.focus_group(0) - md_view = self.window.open_file(file_name) - mdsettings = md_view.settings() + preview_window.focus_group(1) + preview_view = preview_window.new_file() + preview_view.set_scratch(True) + preview_view.settings().set(PREVIEW_VIEW_INFOS, {}) + preview_view.set_name('Preview') - mdsettings.set(PREVIEW_ENABLED, True) - mdsettings.set(PREVIEW_ID, preview.id()) + + preview_window.focus_group(0) + if file_name: + markdown_view = preview_window.open_file(file_name) + else: + markdown_view = preview_window.new_file() + markdown_view.run_command('mdlp_insert', {'point': 0, 'string': content}) + markdown_view.set_scratch(True) + + markdown_view.set_syntax_file(syntax_file) + markdown_view.settings().set(MARKDOWN_VIEW_INFOS, { + "original_window_id": original_window_id + }) def is_enabled(self): - return is_markdown_view(sublime.active_window().active_view()) + # FIXME: is this the best way there is to check if the current syntax is markdown? + # should we only support default markdown? + # what about "md"? + # FIXME: what about other languages, where markdown preview roughly works? + return 'markdown' in self.view.settings().get('syntax').lower() class MarkdownLivePreviewListener(sublime_plugin.EventListener): - def update(self, view): - vsettings = view.settings() - now = time.time() + phantom_sets = { + # markdown_view.id(): phantom set + } - if now - vsettings.get(LAST_UPDATE, 0) < get_settings().get('update_preview_every'): - return - vsettings.set(LAST_UPDATE, now) - if not vsettings.get(PREVIEW_ENABLED): - return - id = vsettings.get(PREVIEW_ID) - if id is None: - raise ValueError('The preview id is None') - preview = get_view_from_id(view.window(), id) - if preview is None: - raise ValueError('The preview is None (id: {})'.format(id)) + # we schedule an update for every key stroke, with a delay of DELAY + # then, we update only if now() - last_update > DELAY + last_update = 0 - show_html(view, preview) - return view, preview + # FIXME: maybe we shouldn't restore the file in the original window... - def on_modified_async(self, view): - if not is_markdown_view(view): # faster than getting the settings + def on_pre_close(self, markdown_view): + """ Close the view in the preview window, and store information for the on_close + listener (see doc there) + """ + if not markdown_view.settings().get(MARKDOWN_VIEW_INFOS): return - delay = get_settings().get('update_preview_every') - if not delay: - self.update(view) + + self.markdown_view = markdown_view + self.preview_window = markdown_view.window() + self.file_name = markdown_view.file_name() + + if self.file_name is None: + total_region = sublime.Region(0, markdown_view.size()) + self.content = markdown_view.substr(total_region) + markdown_view.erase(edit, total_region) else: - sublime.set_timeout(lambda: self.update(view), delay * 1000) + self.content = None - def on_window_command(self, window, command, args): - if command == 'close' and window.settings().get(PREVIEW_WINDOW): - release_phantoms_set(window.id()) - return 'close_window', {} - - def on_activated_async(self, view): - vsettings = view.settings() - - if (is_markdown_view(view) and get_settings().get(ON_OPEN) - and not vsettings.get(PREVIEW_ENABLED) - and vsettings.get('syntax') != 'Packages/MarkdownLivePreview/' + \ - '.sublime/MarkdownLivePreviewSyntax' + \ - '.hidden-tmLanguage' - and not any(filter(lambda window: window.settings().get(PREVIEW_WINDOW) is True, - sublime.windows()))): - sublime.run_command('new_markdown_live_preview') - - - def on_load_async(self, view): - """Check the settings to hide menu, minimap, etc""" - try: - md_view, preview = self.update(view) - except TypeError: - # the function update has returned None + def on_load_async(self, markdown_view): + infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS) + if not infos: return - window = preview.window() - psettings = preview.settings() - show_tabs = psettings.get('show_tabs') - show_minimap = psettings.get('show_minimap') - show_status_bar = psettings.get('show_status_bar') - show_sidebar = psettings.get('show_sidebar') - show_menus = psettings.get('show_menus') + preview_view = markdown_view.window().active_view_in_group(1) - if show_tabs is not None: - window.set_tabs_visible(show_tabs) - if show_minimap is not None: - window.set_minimap_visible(show_minimap) - if show_status_bar is not None: - window.set_status_bar_visible(show_status_bar) - if show_sidebar is not None: - window.set_sidebar_visible(show_sidebar) - if show_menus is not None: - window.set_menu_visible(show_menus) + self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view) + self._update_preview(markdown_view) -class MarkdownLivePreviewClearCacheCommand(sublime_plugin.ApplicationCommand): + def on_close(self, markdown_view): + """ Use the information saved to restore the markdown_view as an original_view + """ + infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS) + if not infos: + return - def run(self): - clear_cache() + assert markdown_view.id() == self.markdown_view.id(), \ + "pre_close view.id() != close view.id()" + + del self.phantom_sets[markdown_view.id()] + + self.preview_window.run_command('close_window') + + # find the window with the right id + original_window = next(window for window in sublime.windows() \ + if window.id() == infos['original_window_id']) + if self.file_name: + original_window.open_file(self.file_name) + else: + assert markdown_view.is_scratch(), "markdown view of an unsaved file should " \ + "be a scratch" + # note here that this is called original_view, because it's what semantically + # makes sense, but this original_view.id() will be different than the one + # that we closed first to reopen in the preview window + # shouldn't cause any trouble though + original_view = original_window.new_file() + original_view.run_command('mdlp_insert', {'point': 0, 'string': self.content}) + + original_view.set_syntax_file(markdown_view.settings().get('syntax')) + + + # here, views are NOT treated independently, which is theoretically wrong + # but in practice, you can only edit one markdown file at a time, so it doesn't really + # matter. + # @min_time_between_call(.5) + def on_modified_async(self, markdown_view): + + infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS) + if not infos: + return + + # we schedule an update, which won't run if an + sublime.set_timeout(partial(self._update_preview, markdown_view), DELAY) + + def _update_preview(self, markdown_view): + # if the buffer id is 0, that means that the markdown_view has been closed + # This check is needed since a this function is used as a callback for when images + # are loaded from the internet (ie. it could finish loading *after* the user + # closes the markdown_view) + if time.time() - self.last_update < DELAY / 1000: + return + + if markdown_view.buffer_id() == 0: + return + + self.last_update = time.time() + + total_region = sublime.Region(0, markdown_view.size()) + markdown = markdown_view.substr(total_region) + + basepath = os.path.dirname(markdown_view.file_name()) + html = markdown2html( + markdown, + basepath, + partial(self._update_preview, markdown_view), + resources + ) + + self.phantom_sets[markdown_view.id()].update([ + sublime.Phantom(sublime.Region(0), html, sublime.LAYOUT_BLOCK, + lambda href: sublime.run_command('open_url', {'url': href})) + ]) + + \ No newline at end of file diff --git a/MarkdownLivePreview.sublime-commands b/MarkdownLivePreview.sublime-commands new file mode 100644 index 0000000..cdca348 --- /dev/null +++ b/MarkdownLivePreview.sublime-commands @@ -0,0 +1,7 @@ +[ + + { + "caption": "MarkdownLivePreview: Open Preview", + "command": "open_markdown_preview" + } +] \ No newline at end of file diff --git a/README.md b/README.md index 02daa1f..5a8cef2 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,18 @@ # MarkdownLivePreview -This is a sublime text **3** plugin that allows you to preview your markdown instantly *in* it! +A simple plugin to preview your markdown as you type right in Sublime Text. +No dependencies! -## Unmaintained +## How to install -I am now using vim. I don't have the energy or the time to maintain this plugin anymore. +It's available on package control! -If anyone is interested in maintaining it, fork it, and submit a PR to package control to make it point to your fork. +## How to contribute -### Dependencies +1. Fork this repo +2. Make your own branch (the name of the branch should be the feature you are + implementing eg. `improve-tables`, `fix-crash-on-multiple-preview` +3. All your code should be formated by black. +4. Send a PR! -**None! There is no dependency!** It uses [markdown2](https://github.com/trentm/python-markdown2) but it's a one file plugin, so it's included in the package. -## Installation - -MarkdownLivePreview is available on the default channel of -[PackageControl](http://packagecontrol.io), which means you just have to - -1. Open the command palette (`ctrl+shift+p`) -2. Search for: `Package Control: Install Package` -3. Search for: `MarkdownLivePreview` -4. hit enter - -to have MarkdownLivePreview working on your computer. Cool right? You can -[thank package control](https://packagecontrol.io/say_thanks) for this. :wink: - -### Usage - -You can choose to enable MarkdownLivePreview by pressing alt+m or selecting in the -command palette `MarkdownLivePreview: Edit Current File`. Note that you need to be editing (simply -having the focus on) a markdown file. Because [Markdown Extended][markdown-extended] did a good job, -it's compatible with this plugin. - -So, once you've run it, it will open a new window, with only your markdown file, with the preview. -Once you're done, close whichever file and it'll close the entire window. - -*Notice that it will close the entire window if you close __whichever__ file. It means that if you* -*open a random file in this window, and then close it, it'll close the entire window still* - -For further infos, please [read the docs](https://math2001.github.io/MarkdownLivePreview/)! - -### Demo - -![demo](demo.gif) - -### Somethings wrong!! - -If you find that something's wrong with this package, you can let me know by raising an issue on the -[GitHub issue tracker][github-issue-tracker] - -### How to open the [README][] - -Some of the package add a command in the menus, others in the command palette, or other nowhere. -None of those options are really good, especially the last one on ST3 because the packages are -compressed. But, fortunately, there is plugin that exists and **will solve this problem** for us -(and he has a really cute name, don't you think?): -[ReadmePlease](https://packagecontrol.io/packages/ReadmePlease). - -[markdown-extended]: https://packagecontrol.io/packages/Markdown%20Extended -[github-issue-tracker]: https://github.com/math2001/MarkdownLivePreview/issues -[st-css-rules]: http://www.sublimetext.com/docs/3/minihtml.html#css -[README]: http://github.com/math2001/MarkdownLivePreview/README.md diff --git a/default.css b/default.css deleted file mode 100644 index 64bde6f..0000000 --- a/default.css +++ /dev/null @@ -1,63 +0,0 @@ -html { - --light-bg: color(var(--background) blend(#999 85%)); - --very-light-bg: color(var(--background) blend(#999 92%)); -} - -body { - padding: 10px; - padding-top: 0px; - font-family: "Open Sans", sans-serif; - background-color: var(--background); - font-size: 15px; -} - -blockquote { - font-style: italic; - display: block; - margin-left: 30px; -} - -code { - padding-left: 0.2rem; - padding-right: 0.2rem; - background-color: var(--light-bg); - margin: 0; - border-radius: 3px; -} - -div.codehilite { - display: block; - margin-top: 20px; - background-color: var(--light-bg); - padding-left: 10px; - padding-top: 5px; - padding-bottom: 5px; - width: 100%; - border-radius: 3px; -} -pre code { - padding: 0; -} - -pre code .space { - color: var(--light-bg); -} - -pre.table { - background-color: var(--background); -} -pre.table code { - background-color: var(--background); - padding: 0; - margin: 0; -} -pre.table code .space { - color: var(--background); -} - - -kbd { - padding: 0 5px; - background-color: var(--very-light-bg); - font-family: "Roboto Mono","Courier New",Courier,monospace; -} diff --git a/demo.gif b/demo.gif deleted file mode 100644 index d4ddb92..0000000 Binary files a/demo.gif and /dev/null differ diff --git a/dependencies.json b/dependencies.json index e7e714a..d0b3031 100644 --- a/dependencies.json +++ b/dependencies.json @@ -1,8 +1,7 @@ { "*": { "*": [ - "bs4", - "pygments" + "bs4" ] } } diff --git a/devListener.py b/devListener.py deleted file mode 100644 index 34e32ff..0000000 --- a/devListener.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- encoding: utf-8 -*- - -import sublime -import sublime_plugin -import os.path - -class MLPDevListener(sublime_plugin.EventListener): - - def on_post_save(self, view): - if not (os.path.dirname(__file__) in view.file_name() and - view.file_name().endswith('.py')): - return - sublime.run_command('reload_plugin', { - 'main': os.path.join(sublime.packages_path(), 'MarkdownLivePreview', - 'MarkdownLivePreview.py'), - 'scripts': ['image_manager', 'functions', 'MLPApi', - 'setting_names'], - 'folders': ['lib'], - 'quiet': True - }) diff --git a/docs/imgs/MarkdownLivePreview-opposite.svg b/docs/imgs/MarkdownLivePreview-opposite.svg deleted file mode 100644 index b11987c..0000000 --- a/docs/imgs/MarkdownLivePreview-opposite.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/docs/imgs/MarkdownLivePreview.svg b/docs/imgs/MarkdownLivePreview.svg deleted file mode 100644 index 37f19e4..0000000 --- a/docs/imgs/MarkdownLivePreview.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/docs/imgs/syntax-specific-settings.png b/docs/imgs/syntax-specific-settings.png deleted file mode 100644 index dc0dccf..0000000 Binary files a/docs/imgs/syntax-specific-settings.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index ddcd7a2..0000000 --- a/docs/index.md +++ /dev/null @@ -1,184 +0,0 @@ -# Welcome to MarkdownLivePreview's documentation! - -MarkdownLivePreview's logo - -MarkdownLivePreview is a [Sublime Text 3][st] plugin to preview your markdown as you type, -*right in Sublime Text itself*, without *any* dependency! - -It's very easy to use, but there's a few things that you might want to be aware of... So, let's -get started - -## Installation - -### Using Package Control - -You can really easily install MarkdownLivePreview by using [Package Control][pck-con]. - -If it's not already, you need to [install it][install-pck-con] first. - -!!! note - If you're using the latest build of Sublime Text 3, you can just do - *Tools → Install Package Control…* - -- Open up the command palette (ctrl+shift+p) -- Search up `Package Control: Install Package` (might take a few seconds) -- In the panel that just showed up, search for `MarkdownLivePreview` - -Done! You have now access to every single features of MarkdownLivePreview! :wink: - -### Using `git` - -```sh -$ cd "%APPDATA%\Sublime Text 3\Packages" # on Windows -$ cd ~/Library/Application\ Support/Sublime\ Text\ 3 # on Mac -$ cd ~/.config/sublime-text-3 # on Linux - -$ git clone "https://github.com/math2001/MarkdownLivePreview" -``` - -> So, which one do I pick?! - -I depends of what you want to do. If you want to just use MarkdownLivePreview, pick the first -solution, you'll get every update automatically. But if you want to contribute, then choose the -second solution. - -## Usage - -### Previewing - -As told in the introduction, MarkdownLivePreview is very easy to use: - -- open a markdown file -- press alt+m -- or select in the command palette `MarkdownLivePreview: Edit Current File` - -!!! note - The preview of unsaved markdown files is currently not supported. It should be fixed soon. - -!!! tip - [Markdown Extended][] is supported too! - -That's it. That's all you need to do to preview your markdown! - -### Settings - -To edit MarkdownLivePreview's settings, you just need to search in the command palette -`Preferences: MarkdownLivePreview Settings`, or from the menus: -*Preferences → Package Settings → MarkdownLivePreview → Settings* - -Do not edit the left file (by default, you cannot), but the right one. This right file will -override the default one (on the left), and will be saved in your `User` folder, which makes it easy -to back up. - -- `markdown_live_preview_on_open`: if set to `true`, as soon as you open a markdown file, the -preview window will popup (thanks to[@ooing][] for its [suggestion][@ooing suggestion]). Default to -`false` -- `load_from_internet_when_starts`: every images that starts with any of the string specified in -this list will be loaded from internet. Default to `["http://", "https://"]` -- `header_action`: If you're writing a blog with some markdown and a static website generator, you -probably have a YAML header. By default, this header will be displayed in a `pre` block. If you want -to hide it, then just change the value to `remove`. Thanks to [@tanhanjay][] for -[letting me know][front-matter-issue]! -- `keep_open_when_opening_preview`: Each time the preview window is opened, the original markdown -view is closed. If you want to keep it opened, just set this setting to `true` - -### Custom CSS - -If you want to, you can add custom `CSS` to the MarkdownLivePreview's default stylesheet. - -Just search for `MarkdownLivePreview: Edit Custom CSS File` in the command palette -(ctrl+shift+p). It will open a file in which you can add some CSS that will be *added* to -the normal CSS. - -!!! bug - Comments in the CSS is interpreted weirdly by Sublime Text's phantoms. After a few tests, I - think that everything that is bellow a comment is ignored. - - If you want to be sure that your CSS works, don't put any comments in it - -#### Share your tweaks! - -If you think that other users would enjoy your added CSS, then raise an issue, or PR the -[GitHub repo][] to share your tweaks! - -### Clearing the cache - -MarkdownLivePreview has a cache system to store images you load from internet. You can clear this -cache by searching up in the command palette `MarkdownLivePreview: Clear the cache`. - -!!! tip - The cache is one simple file called `MarkdownLivePreviewCache`, which is located in your temp - folder. To know where it is, you can open the Sublime Text console (ctrl+` or - *View → Show Console*), and paste this in: - - ```python - import tempfile; print(tempfile.gettempdir()) - ``` - -### Custom settings for the preview - -Sublime Text makes it easy to set custom settings for a specific *type* of view. For example, -`markdown`, `python`, etc. MarkdownLivePreview takes advantage of that: the preview view (the view -on the right) is a specific syntax (called — sorry for the originality — -`MarkdownLivePreviewSyntax`). So, to change this, you can focus the right view, open up the command -palette (ctrl+shift+p), and search up `Preferences: Settings — Syntax Specific`. In here, -you can specify any settings that is going to be applied only to this view. - -### The hacky part - -In fact, MarkdownLivePreview parses those settings, and looks for specific ones: - -- `show_tabs` -- `show_minimap` -- `show_status_bar` -- `show_sidebar` -- `show_menus` - -Those settings aren't supported by default because they affect the entire *window* instead of just -the view. But MarkdownLivePreview will look for them in your *preview*'s settings, and hide/show the -tabs, the minimap, etc... - -As you probably guessed those settings takes a bool for value (`true` or `false`). - -### Recommendation - -Here's what I'd recommend for your MarkdownLivePreviewSyntax's settings (and what I use): - -```json -{ - "show_menus": false, - "show_tabs": false, - "show_minimap": false, - "gutter": false, - "rulers": [], - "word_wrap": true -} -``` - -And here's what you'll get (With the awesome [Boxy Theme][] and its [Monokai Color Scheme][]): - -![MarkdownLivePreview Screenshoot](imgs/syntax-specific-settings.png) - -!!! tip - On Windows at least, you can press alt to focus (so show) the menus, even if they're - originally hidden. - -That's it! I hope you'll enjoy using this package! If it's the case, please let your friends know -about it, and even myself by sending me a [tweet][] or staring the repo! - - -[st]: https://sublimetext.com -[Markdown Extended]: https://packagecontrol.io/packages/Markdown%20Extended -[pck-con]: https://packagecontrol.io -[install-pck-con]: https://packagecontrol.io/installation -[tweet]: https://twitter.com/_math2001 -[GitHub repo]: https://github.com/math2001/MarkdownLivePreview/issues -[@ooing]: https://github.com/ooing -[@ooing suggestion]: https://github.com/math2001/MarkdownLivePreview/issues/7#issue-199464852 -[@tanhanjay]: https://github.com/tanhanjay -[front-matter-issue]: https://github.com/math2001/MarkdownLivePreview/issues/17 -[Boxy Theme]: https://packagecontrol.io/packages/Boxy%20Theme -[Monokai Color Scheme]: https://github.com/ihodev/sublime-boxy#boxy-monokai--predawn diff --git a/docs/license.md b/docs/license.md deleted file mode 100644 index 0bf6cd0..0000000 --- a/docs/license.md +++ /dev/null @@ -1,24 +0,0 @@ -This project is published under MIT license. - -> The MIT License is a permissive license that is short and to the point. It lets people do anything -> they want with your code as long as they provide attribution back to you and don’t hold you -> liable. -> -> — *from [choosealicense.com](http://choosealicense.com), by [GitHub](https://github.com)* - -Copyright 2017 Mathieu PATUREL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software -andassociated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES -OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/escape_amp.py b/escape_amp.py deleted file mode 100644 index 5b3c078..0000000 --- a/escape_amp.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- encoding: utf-8 -*- - -import re - -__all__ = ['escape_amp'] - -RE_REPLACE_AMPERSAND = re.compile(r'&(\w*)(;)?') - -def replace(matchobj): - if matchobj.group(2): - return matchobj.group(0) - else: - return matchobj.group(0).replace('&', '&') - -def escape_amp(text): - return RE_REPLACE_AMPERSAND.sub(replace, text) - -def run_tests(): - tests = [ - ['&', '&'], - ['&', '&amp'], - ['&', '&'], - ['& &hello &bonjour;', '& &hello &bonjour;'] - ] - fails = 0 - for i, (subject, result) in enumerate(tests): - if RE_REPLACE_AMPERSAND.sub(replace, subject) != result: - # CSW: ignore - print('TEST FAIL ({i}): {subject!r} escaped did not match {result!r}'.format(**locals())) - fails += 1 - if fails == 0: - # CSW: ignore - print("SUCCESS: every tests ({}) passed successfully!".format(len(tests))) - else: - # CSW: ignore - print("{} test{} failed".format(fails, 's' if fails > 1 else '')) - -if __name__ == '__main__': - run_tests() diff --git a/example.md b/example.md deleted file mode 100644 index 8b3e57c..0000000 --- a/example.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Demo -description: Preview your markdown right in Sublime Text! -hope: You'll enjoy using it! ---- - -# Hello world - - - -And `` - -Some `inline code` with *italic*, **bold** text, and ~~strike through~~. - -```python -import this -if you is moods.curious: - print('then do it!') -``` - -ctrl+\` or *View → Show Console* and paste `import this`! - -> Perfect programmers do NOT need comments. - -- to be efficient -- you need -- todos - - -| ID | Name | -|-----------|-------| -| 56 | Matt | -| 42 | Colin | -| 23 | Lisa | -| 45 | John | -| `` | `><` | - -![Sublime Text Logo](https://upload.wikimedia.org/wikipedia/en/4/4c/Sublime_Text_Logo.png) - -Some plugin I just *need*: - -- [PackageResourceReviewer](https://packagecontrol.io/packages/PackageResourceViewer) -- [Boxy Theme](https://packagecontrol.io/packages/Boxy%20Theme) -- [Markdown Preview](https://packagecontrol.io/packages/Markdown%20Preview) -- [FileManager](https://packagecontrol.io/packages/FileManager) -- [PlainTasks](https://packagecontrol.io/packages/PlainTasks) -- [JSONComma](https://packagecontrol.io/packages/JSONComma) diff --git a/functions.py b/functions.py deleted file mode 100644 index d0d7221..0000000 --- a/functions.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- encoding: utf-8 -*- -import base64 -import os.path -import sublime -import re -from .image_manager import ImageManager -from .lib.pygments_from_theme import pygments_from_theme -from bs4 import BeautifulSoup, Comment as html_comment - -def plugin_loaded(): - global error404, loading, DEFAULT_STYLE, USER_STYLE_FILE - loading = sublime.load_resource('Packages/MarkdownLivePreview/loading.txt') - error404 = sublime.load_resource('Packages/MarkdownLivePreview/404.txt') - - DEFAULT_STYLE = sublime.load_resource('Packages/MarkdownLivePreview/default.css') - USER_STYLE_FILE = os.path.join(sublime.packages_path(), 'User', "MarkdownLivePreview.css") - -MATCH_YAML_HEADER = re.compile(r'^([\-\+])\1{2}\n(?P.+)\n\1{3}\n', re.DOTALL) - -def strip_html_comments(html): - soup = BeautifulSoup(html, 'html.parser') - for element in soup.find_all(text=lambda text: isinstance(text, html_comment)): - element.extract() - return str(soup) - -def manage_header(md, action): - matchobj = MATCH_YAML_HEADER.match(md) - if not matchobj: - return md - if action == 'remove': - return md[len(matchobj.group(0)):] - elif action == 'wrap_in_pre': - return '
' + matchobj.group('content') + '
' \ - + md[len(matchobj.group(0)):] - - raise ValueError('Got an unknown action: "{}"'.format(action)) - -def get_preview_name(md_view): - file_name = md_view.file_name() - name = md_view.name() \ - or os.path.basename(file_name) if file_name else None \ - or 'Untitled' - return name + ' - Preview' - -def replace_img_src_base64(html, basepath): - soup = BeautifulSoup(html, 'html.parser') - load_from_internet_starters = get_settings().get('load_from_internet_when_starts') - for img in soup.find_all('img'): - if img['src'].startswith('data:image/'): - continue - elif img['src'].startswith(tuple(load_from_internet_starters)): - image = ImageManager.get(img['src']) or loading - else: # this is a local image - image = to_base64(os.path.join(basepath, img['src'])) - - img['src'] = image - - return str(soup) - -def is_markdown_view(view): - return 'markdown' in view.scope_name(0) - -def to_base64(path=None, content=None): - if path is None and content is None: - return error404 - elif content is None and path is not None: - try: - with open(path, 'rb') as fp: - content = fp.read() - except (FileNotFoundError, OSError): - return error404 - - return 'data:image/png;base64,' + ''.join([chr(el) for el in list(base64.standard_b64encode(content))]) - -def md(*t, **kwargs): - sublime.message_dialog(kwargs.get('sep', '\n').join([str(el) for el in t])) - -def sm(*t, **kwargs): - sublime.status_message(kwargs.get('sep', ' ').join([str(el) for el in t])) - -def em(*t, **kwargs): - sublime.error_message(kwargs.get('sep', ' ').join([str(el) for el in t])) - -def mini(val, min): - if val < min: - return min - return val - -def get_content_till(string, char_to_look_for, start=0): - i = start - while i < len(string): - if string[i] == char_to_look_for: - return string[start:i], i - i += 1 - -def get_view_content(view): - return view.substr(sublime.Region(0, view.size())) - -def get_view_from_id(window, id): - if not isinstance(id, int): - return - for view in window.views(): - if view.id() == id: - return view - -def get_settings(): - return sublime.load_settings('MarkdownLivePreview.sublime-settings') - - -def _pre_with_spaces(code): - for tag in code.find_all(text=True): - tag.replace_with(BeautifulSoup(str(tag).replace('\t', ' ' * 4).replace(' ', '.').replace('\n', '
'), 'html.parser')) - return code - -def pre_with_br(html): - """Because the phantoms of sublime text does not support
 blocks
-    this function replaces every \n with a 
in a
"""
-    soup = BeautifulSoup(html, 'html.parser')
-    for pre in soup.find_all('pre'):
-        code = pre.find('code')
-        code.replace_with(_pre_with_spaces(code))
-    return str(soup)
-
-
-def get_style(color_scheme):
-    css = DEFAULT_STYLE
-    if os.path.exists(USER_STYLE_FILE):
-        with open(USER_STYLE_FILE) as fp:
-            css += '\n' + fp.read() + '\n'
-    if color_scheme and color_scheme.endswith('.tmTheme'):
-        css += pygments_from_theme(get_resource(color_scheme))
-    return ''.join([line.strip() + ' ' for line in css.splitlines()])
-
-def get_resource(resource):
-    if os.path.exists(os.path.join(sublime.packages_path(), '..', resource)):
-        with open(os.path.join(sublime.packages_path(), '..', resource), encoding='utf-8') as fp:
-            return fp.read()
-    else:
-        return sublime.load_resource(resource)
diff --git a/image_manager.py b/image_manager.py
deleted file mode 100644
index c69f274..0000000
--- a/image_manager.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# -*- encoding: utf-8 -*-
-
-import os.path
-import tempfile
-import sublime
-from threading import Thread
-import urllib.request, urllib.error
-from .functions import *
-
-CACHE_FILE = os.path.join(tempfile.gettempdir(),
-                          'MarkdownLivePreviewCache.txt')
-TIMEOUT = 20 # seconds
-
-SEPARATOR = '---%cache%--'
-
-def get_base64_saver(loading, url):
-    def callback(content):
-        if isinstance(content, urllib.error.HTTPError):
-            if content.getcode() == 404:
-                loading[url] = 404
-                return
-        elif isinstance(content, urllib.error.URLError):
-            if (content.reason.errno == 11001 and
-                content.reason.strerror == 'getaddrinfo failed'):
-                loading[url] = 404
-                return
-            return sublime.error_message('An unexpected error has occured: ' +
-                                         str(content))
-        loading[url] = to_base64(content=content)
-
-    return callback
-
-def get_cache_for(imageurl):
-    if not os.path.exists(CACHE_FILE):
-        return
-    with open(CACHE_FILE) as fp:
-        for line in fp.read().splitlines():
-            url, base64 = line.split(SEPARATOR, 1)
-            if url == imageurl:
-                return base64
-
-def cache(imageurl, base64):
-    with open(CACHE_FILE, 'a') as fp:
-        fp.write(imageurl + SEPARATOR + base64 + '\n')
-
-class ImageLoader(Thread):
-
-    def __init__(self, url, callback):
-        Thread.__init__(self)
-        self.url = url
-        self.callback = callback
-
-    def run(self):
-        try:
-            page = urllib.request.urlopen(self.url, None, TIMEOUT)
-        except Exception as e:
-            self.callback(e)
-        else:
-            self.callback(page.read())
-
-
-class ImageManager(object):
-
-    """
-        Usage:
-
-        >>> image = ImageManager.get('http://domain.com/image.png')
-        >>> image = ImageManager.get('http://domain.com/image.png')
-        # still loading (this is a comment, no an outputed text), it doesn't
-        # run an other request
-        >>> image = ImageManager.get('http://domain.com/image.png')
-        'data:image/png;base64,....'
-    """
-    loading = {}
-
-    @staticmethod
-    def get(imageurl, user_callback=None):
-
-        cached = get_cache_for(imageurl)
-        if cached:
-            return cached
-        elif imageurl in ImageManager.loading.keys():
-            # return None (the file is still loading, already made a request)
-            # return string the base64 of the url (which is going to be cached)
-            temp_cached = ImageManager.loading[imageurl]
-            if temp_cached == 404:
-                return to_base64('404.png')
-            if temp_cached:
-                cache(imageurl, temp_cached)
-                del ImageManager.loading[imageurl]
-            return temp_cached
-        else:
-            # load from internet
-            ImageManager.loading[imageurl] = None
-            callback = get_base64_saver(ImageManager.loading, imageurl)
-            loader = ImageLoader(imageurl, callback)
-            loader.start()
-            sublime.set_timeout_async(lambda: loader.join(), TIMEOUT * 1000)
diff --git a/imgs/README.md b/imgs/README.md
deleted file mode 100644
index ea4491e..0000000
--- a/imgs/README.md
+++ /dev/null
@@ -1 +0,0 @@
-There is images here, allthough they aren't of any use for the plugin. They're just the image that I used to generate the base64 (404.txt and loading.txt)
diff --git a/lib/markdown2.py b/lib/markdown2.py
index f202cee..49bedd0 100644
--- a/lib/markdown2.py
+++ b/lib/markdown2.py
@@ -1,13 +1,8 @@
-# CSW: ignore
-# -*- encoding: utf-8 -*-
-
 #!/usr/bin/env python
 # Copyright (c) 2012 Trent Mick.
 # Copyright (c) 2007-2008 ActiveState Corp.
 # License: MIT (http://www.opensource.org/licenses/mit-license.php)
 
-from __future__ import generators
-
 r"""A fast and complete Python implementation of Markdown.
 
 [from http://daringfireball.net/projects/markdown/]
@@ -55,6 +50,8 @@ see  for details):
   implemented in other Markdown processors (tho not in Markdown.pl v1.0.1).
 * header-ids: Adds "id" attributes to headers. The id value is a slug of
   the header text.
+* highlightjs-lang: Allows specifying the language which used for syntax
+  highlighting when using fenced-code-blocks and highlightjs.
 * html-classes: Takes a dict mapping html tag names (lowercase) to a
   string to use for a "class" tag attribute. Currently only supports "img",
   "table", "pre" and "code" tags. Add an issue if you require this for other
@@ -78,6 +75,7 @@ see  for details):
   and ellipses.
 * spoiler: A special kind of blockquote commonly hidden behind a
   click on SO. Syntax per .
+* strike: text inside of double tilde is ~~strikethrough~~
 * tag-friendly: Requires atx style headers to have a space between the # and
   the header text. Useful for applications that require twitter style tags to
   pass through the parser.
@@ -98,20 +96,18 @@ see  for details):
 #   not yet sure if there implications with this. Compare 'pydoc sre'
 #   and 'perldoc perlre'.
 
-__version_info__ = (2, 3, 2)
+__version_info__ = (2, 3, 9)
 __version__ = '.'.join(map(str, __version_info__))
 __author__ = "Trent Mick"
 
 import sys
 import re
 import logging
-try:
-    from hashlib import md5
-except ImportError:
-    from md5 import md5
+from hashlib import sha256
 import optparse
 from random import random, randint
 import codecs
+from collections import defaultdict
 try:
     from urllib import quote_plus
 except ImportError:
@@ -120,11 +116,6 @@ except ImportError:
 
 # ---- Python version compat
 
-if sys.version_info[:2] < (2, 4):
-    def reversed(sequence):
-        for i in sequence[::-1]:
-            yield i
-
 # Use `bytes` for byte strings and `unicode` for unicode strings (str in Py3).
 if sys.version_info[0] <= 2:
     py3 = False
@@ -147,13 +138,19 @@ DEFAULT_TAB_WIDTH = 4
 
 
 SECRET_SALT = bytes(randint(0, 1000000))
+# MD5 function was previously used for this; the "md5" prefix was kept for
+# backwards compatibility.
 def _hash_text(s):
-    return 'md5-' + md5(SECRET_SALT + s.encode("utf-8")).hexdigest()
+    return 'md5-' + sha256(SECRET_SALT + s.encode("utf-8")).hexdigest()[32:]
 
 # Table of hash values for escaped characters:
 g_escape_table = dict([(ch, _hash_text(ch))
     for ch in '\\`*_{}[]()>#+-.!'])
 
+# Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+#   http://bumppo.net/projects/amputator/
+_AMPERSAND_RE = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)')
+
 
 # ---- exceptions
 class MarkdownError(Exception):
@@ -165,6 +162,7 @@ class MarkdownError(Exception):
 def markdown_path(path, encoding="utf-8",
                   html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
                   safe_mode=None, extras=None, link_patterns=None,
+                  footnote_title=None, footnote_return_symbol=None,
                   use_file_vars=False):
     fp = codecs.open(path, 'r', encoding)
     text = fp.read()
@@ -172,16 +170,21 @@ def markdown_path(path, encoding="utf-8",
     return Markdown(html4tags=html4tags, tab_width=tab_width,
                     safe_mode=safe_mode, extras=extras,
                     link_patterns=link_patterns,
+                    footnote_title=footnote_title,
+                    footnote_return_symbol=footnote_return_symbol,
                     use_file_vars=use_file_vars).convert(text)
 
 
 def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
              safe_mode=None, extras=None, link_patterns=None,
-             use_file_vars=False):
+             footnote_title=None, footnote_return_symbol=None,
+             use_file_vars=False, cli=False):
     return Markdown(html4tags=html4tags, tab_width=tab_width,
                     safe_mode=safe_mode, extras=extras,
                     link_patterns=link_patterns,
-                    use_file_vars=use_file_vars).convert(text)
+                    footnote_title=footnote_title,
+                    footnote_return_symbol=footnote_return_symbol,
+                    use_file_vars=use_file_vars, cli=cli).convert(text)
 
 
 class Markdown(object):
@@ -206,7 +209,9 @@ class Markdown(object):
     _ws_only_line_re = re.compile(r"^[ \t]+$", re.M)
 
     def __init__(self, html4tags=False, tab_width=4, safe_mode=None,
-                 extras=None, link_patterns=None, use_file_vars=False):
+                 extras=None, link_patterns=None,
+                 footnote_title=None, footnote_return_symbol=None,
+                 use_file_vars=False, cli=False):
         if html4tags:
             self.empty_element_suffix = ">"
         else:
@@ -231,13 +236,23 @@ class Markdown(object):
                 extras = dict([(e, None) for e in extras])
             self.extras.update(extras)
         assert isinstance(self.extras, dict)
-        if "toc" in self.extras and "header-ids" not in self.extras:
-            self.extras["header-ids"] = None   # "toc" implies "header-ids"
+
+        if "toc" in self.extras:
+            if "header-ids" not in self.extras:
+                self.extras["header-ids"] = None   # "toc" implies "header-ids"
+
+            if self.extras["toc"] is None:
+                self._toc_depth = 6
+            else:
+                self._toc_depth = self.extras["toc"].get("depth", 6)
         self._instance_extras = self.extras.copy()
 
         self.link_patterns = link_patterns
+        self.footnote_title = footnote_title
+        self.footnote_return_symbol = footnote_return_symbol
         self.use_file_vars = use_file_vars
         self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M)
+        self.cli = cli
 
         self._escape_table = g_escape_table.copy()
         if "smarty-pants" in self.extras:
@@ -255,16 +270,26 @@ class Markdown(object):
             self.footnotes = {}
             self.footnote_ids = []
         if "header-ids" in self.extras:
-            self._count_from_header_id = {}  # no `defaultdict` in Python 2.4
+            self._count_from_header_id = defaultdict(int)
         if "metadata" in self.extras:
             self.metadata = {}
 
     # Per  "rel"
     # should only be used in  tags with an "href" attribute.
-    _a_nofollow = re.compile(r"<(a)([^>]*href=)", re.IGNORECASE)
+    _a_nofollow = re.compile(r"""
+        <(a)
+        (
+            [^>]*
+            href=   # href is required
+            ['"]?   # HTML5 attribute values do not have to be quoted
+            [^#'"]  # We don't want to match href values that start with # (like footnotes)
+        )
+        """,
+        re.IGNORECASE | re.VERBOSE
+    )
 
     # Opens the linked document in a new window or tab
-    # should only used in  tags with an "target" attribute.
+    # should only used in  tags with an "href" attribute.
     # same with _a_nofollow
     _a_blank = _a_nofollow
 
@@ -366,11 +391,21 @@ class Markdown(object):
         if "target-blank-links" in self.extras:
             text = self._a_blank.sub(r'<\1 target="_blank"\2', text)
 
+        if "toc" in self.extras and self._toc:
+            self._toc_html = calculate_toc_html(self._toc)
+
+            # Prepend toc html to output
+            if self.cli:
+                text = '{}\n{}'.format(self._toc_html, text)
+
         text += "\n"
 
+        # Attach attrs to output
         rv = UnicodeWithAttrs(text)
-        if "toc" in self.extras:
-            rv._toc = self._toc
+
+        if "toc" in self.extras and self._toc:
+            rv.toc_html = self._toc_html
+
         if "metadata" in self.extras:
             rv.metadata = self.metadata
         return rv
@@ -402,30 +437,33 @@ class Markdown(object):
     #
     #   # header
     _meta_data_pattern = re.compile(r'^(?:---[\ \t]*\n)?(.*:\s+>\n\s+[\S\s]+?)(?=\n\w+\s*:\s*\w+\n|\Z)|([\S\w]+\s*:(?! >)[ \t]*.*\n?)(?:---[\ \t]*\n)?', re.MULTILINE)
-    _key_val_pat = re.compile("[\S\w]+\s*:(?! >)[ \t]*.*\n?", re.MULTILINE)
+    _key_val_pat = re.compile(r"[\S\w]+\s*:(?! >)[ \t]*.*\n?", re.MULTILINE)
     # this allows key: >
     #                   value
     #                   conutiues over multiple lines
     _key_val_block_pat = re.compile(
         "(.*:\s+>\n\s+[\S\s]+?)(?=\n\w+\s*:\s*\w+\n|\Z)", re.MULTILINE)
+    _meta_data_fence_pattern = re.compile(r'^---[\ \t]*\n', re.MULTILINE)
+    _meta_data_newline = re.compile("^\n", re.MULTILINE)
 
     def _extract_metadata(self, text):
-        match = re.findall(self._meta_data_pattern, text)
-
-        if not match:
-            return text
-
-        last_item = list(filter(None, match[-1]))[0]
-        end_of_metadata = text.index(last_item)+len(last_item)
         if text.startswith("---"):
-            # add 8 charachters for opening and closing
-            # and since indexing starts at 0 we add a step
-            tail = text[end_of_metadata+4:]
+            fence_splits = re.split(self._meta_data_fence_pattern, text, maxsplit=2)
+            metadata_content = fence_splits[1]
+            match = re.findall(self._meta_data_pattern, metadata_content)
+            if not match:
+                return text
+            tail = fence_splits[2]
         else:
-            tail = text[end_of_metadata:]
+            metadata_split = re.split(self._meta_data_newline, text, maxsplit=1)
+            metadata_content = metadata_split[0]
+            match = re.findall(self._meta_data_pattern, metadata_content)
+            if not match:
+                return text
+            tail = metadata_split[1]
 
-        kv = re.findall(self._key_val_pat, text)
-        kvm = re.findall(self._key_val_block_pat, text)
+        kv = re.findall(self._key_val_pat, metadata_content)
+        kvm = re.findall(self._key_val_block_pat, metadata_content)
         kvm = [item.replace(": >\n", ":", 1) for item in kvm]
 
         for item in kv + kvm:
@@ -957,12 +995,14 @@ class Markdown(object):
 
     def _table_sub(self, match):
         trim_space_re = '^[ \t\n]+|[ \t\n]+$'
-        trim_bar_re = '^\||\|$'
+        trim_bar_re = r'^\||\|$'
+        split_bar_re = r'^\||(?' % self._html_class_str_from_tag('table'), '
', ''] - cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", head)).split('|')] + cols = [re.sub(escape_bar_re, '|', cell.strip()) for cell in re.split(split_bar_re, re.sub(trim_bar_re, "", re.sub(trim_space_re, "", head)))] for col_idx, col in enumerate(cols): hlines.append(' %s' % ( align_from_col_idx.get(col_idx, ''), @@ -987,7 +1027,7 @@ class Markdown(object): hlines.append('') for line in body.strip('\n').split('\n'): hlines.append('') - cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", line)).split('|')] + cols = [re.sub(escape_bar_re, '|', cell.strip()) for cell in re.split(split_bar_re, re.sub(trim_bar_re, "", re.sub(trim_space_re, "", line)))] for col_idx, col in enumerate(cols): hlines.append(' %s' % ( align_from_col_idx.get(col_idx, ''), @@ -1071,6 +1111,9 @@ class Markdown(object): text = self._escape_special_chars(text) # Process anchor and image tags. + if "link-patterns" in self.extras: + text = self._do_link_patterns(text) + text = self._do_links(text) # Make links out of things like `` @@ -1078,9 +1121,6 @@ class Markdown(object): # delimiters in inline links like [this](). text = self._do_auto_links(text) - if "link-patterns" in self.extras: - text = self._do_link_patterns(text) - text = self._encode_amps_and_angles(text) if "strike" in self.extras: @@ -1160,7 +1200,7 @@ class Markdown(object): self.html_spans[key] = sanitized tokens.append(key) else: - tokens.append(token) + tokens.append(self._encode_incomplete_tags(token)) is_html_markup = not is_html_markup return ''.join(tokens) @@ -1353,7 +1393,7 @@ class Markdown(object): if is_img: img_class_str = self._html_class_str_from_tag("img") result = '%s= anchor_allowed_pos: - if self.safe_mode and not self._safe_protocols.match(url): + safe_link = self._safe_protocols.match(url) or url.startswith('#') + if self.safe_mode and not safe_link: result_head = '' % (title_str) else: - result_head = '' % (_urlencode(url, safe_mode=self.safe_mode), title_str) - result = '%s%s' % (result_head, _xml_escape_attr(link_text)) + result_head = '' % (_html_escape_url(url, safe_mode=self.safe_mode), title_str) + result = '%s%s' % (result_head, link_text) if "smarty-pants" in self.extras: result = result.replace('"', self._escape_table['"']) # allowed from curr_pos on, from @@ -1408,7 +1449,7 @@ class Markdown(object): if is_img: img_class_str = self._html_class_str_from_tag("img") result = '%s 1: header_id += '-%s' % self._count_from_header_id[header_id] - else: - self._count_from_header_id[header_id] = 1 + return header_id _toc = None def _toc_add_entry(self, level, id, name): + if level > self._toc_depth: + return if self._toc is None: self._toc = [] self._toc.append((level, id, self._unescape_special_chars(name))) @@ -1491,7 +1534,9 @@ class Markdown(object): _h_re_tag_friendly = re.compile(_h_re_base % '+', re.X | re.M) def _h_sub(self, match): - if match.group(1) is not None: + if match.group(1) is not None and match.group(3) == "-": + return match.group(1) + elif match.group(1) is not None: # Setext header n = {"=": 1, "-": 2}[match.group(3)[0]] header_group = match.group(2) @@ -1610,16 +1655,16 @@ class Markdown(object): re.M | re.X | re.S) _task_list_item_re = re.compile(r''' - (\[[\ x]\])[ \t]+ # tasklist marker = \1 + (\[[\ xX]\])[ \t]+ # tasklist marker = \1 (.*) # list item text = \2 ''', re.M | re.X | re.S) - _task_list_warpper_str = r'

%s

' + _task_list_warpper_str = r' %s' def _task_list_item_sub(self, match): marker = match.group(1) item_text = match.group(2) - if marker == '[x]': + if marker in ['[x]','[X]']: return self._task_list_warpper_str % ('checked ', item_text) elif marker == '[ ]': return self._task_list_warpper_str % ('', item_text) @@ -1728,7 +1773,8 @@ class Markdown(object): codeblock = rest.lstrip("\n") # Remove lexer declaration line. formatter_opts = self.extras['code-color'] or {} - if lexer_name: + # Use pygments only if not using the highlightjs-lang extra + if lexer_name and "highlightjs-lang" not in self.extras: def unhash_code(codeblock): for key, sanitized in list(self.html_spans.items()): codeblock = codeblock.replace(key, sanitized) @@ -1741,7 +1787,7 @@ class Markdown(object): codeblock = codeblock.replace(old, new) return codeblock lexer = self._get_pygments_lexer(lexer_name) - if lexer and self.extras.get('no-code-highlighting', True) is True: + if lexer: codeblock = unhash_code( codeblock ) colored = self._color_with_pygments(codeblock, lexer, **formatter_opts) @@ -1749,7 +1795,12 @@ class Markdown(object): codeblock = self._encode_code(codeblock) pre_class_str = self._html_class_str_from_tag("pre") - code_class_str = self._html_class_str_from_tag("code") + + if "highlightjs-lang" in self.extras and lexer_name: + code_class_str = ' class="%s"' % lexer_name + else: + code_class_str = self._html_class_str_from_tag("code") + return "\n\n%s\n\n\n" % ( pre_class_str, code_class_str, codeblock) @@ -1788,7 +1839,7 @@ class Markdown(object): _fenced_code_block_re = re.compile(r''' (?:\n+|\A\n?) - ^```([\w+-]+)?[ \t]*\n # opening fence, $1 = optional lang + ^```\s*?([\w+-]+)?\s*?\n # opening fence, $1 = optional lang (.*?) # $2 = code block content ^```[ \t]*\n # closing fence ''', re.M | re.X | re.S) @@ -1930,6 +1981,13 @@ class Markdown(object): text = text.replace("...", "…") text = text.replace(" . . . ", "…") text = text.replace(". . .", "…") + + # TODO: Temporary hack to fix https://github.com/trentm/python-markdown2/issues/150 + if "footnotes" in self.extras and "footnote-ref" in text: + # Quotes in the footnote back ref get converted to "smart" quotes + # Change them back here to ensure they work. + text = text.replace('class="footnote-ref”', 'class="footnote-ref"') + return text _block_quote_base = r''' @@ -2001,8 +2059,13 @@ class Markdown(object): # text (issue 33). Note the `[-1]` is a quick way to # consider numeric bullets (e.g. "1." and "2.") to be # equal. - if (li and len(li.group(2)) <= 3 and li.group("next_marker") - and li.group("marker")[-1] == li.group("next_marker")[-1]): + if (li and len(li.group(2)) <= 3 + and ( + (li.group("next_marker") and li.group("marker")[-1] == li.group("next_marker")[-1]) + or + li.group("next_marker") is None + ) + ): start = li.start() cuddled_list = self._do_lists(graf[start:]).rstrip("\n") assert cuddled_list.startswith("
" % indent()) + return '\n'.join(lines) + '\n' + + class UnicodeWithAttrs(unicode): """A subclass of unicode used for the return value of conversion to possibly attach some attributes. E.g. the "toc_html" attribute when the "toc" extra is used. """ metadata = None - _toc = None - def toc_html(self): - """Return the HTML for the current TOC. - - This expects the `_toc` attribute to have been set on this instance. - """ - if self._toc is None: - return None - - def indent(): - return ' ' * (len(h_stack) - 1) - lines = [] - h_stack = [0] # stack of header-level numbers - for level, id, name in self._toc: - if level > h_stack[-1]: - lines.append("%s
    " % indent()) - h_stack.append(level) - elif level == h_stack[-1]: - lines[-1] += "" - else: - while level < h_stack[-1]: - h_stack.pop() - if not lines[-1].endswith(""): - lines[-1] += "" - lines.append("%s
" % indent()) - lines.append('%s
  • %s' % ( - indent(), id, name)) - while len(h_stack) > 1: - h_stack.pop() - if not lines[-1].endswith("
  • "): - lines[-1] += "" - lines.append("%s" % indent()) - return '\n'.join(lines) + '\n' - toc_html = property(toc_html) + toc_html = None ## {{{ http://code.activestate.com/recipes/577257/ (r1) _slugify_strip_re = re.compile(r'[^\w\s-]') @@ -2433,8 +2526,9 @@ def _xml_escape_attr(attr, skip_single_quote=True): By default this doesn't bother with escaping `'` to `'`, presuming that the tag attribute is surrounded by double quotes. """ + escaped = _AMPERSAND_RE.sub('&', attr) + escaped = (attr - .replace('&', '&') .replace('"', '"') .replace('<', '<') .replace('>', '>')) @@ -2457,12 +2551,15 @@ def _xml_encode_email_char_at_random(ch): return '&#%s;' % ord(ch) -def _urlencode(attr, safe_mode=False): - """Replace special characters in string using the %xx escape.""" +def _html_escape_url(attr, safe_mode=False): + """Replace special characters that are potentially malicious in url string.""" + escaped = (attr + .replace('"', '"') + .replace('<', '<') + .replace('>', '>')) if safe_mode: - escaped = quote_plus(attr).replace('+', ' ') - else: - escaped = attr.replace('"', '%22') + escaped = escaped.replace('+', ' ') + escaped = escaped.replace("'", "'") return escaped @@ -2587,7 +2684,8 @@ def main(argv=None): html4tags=opts.html4tags, safe_mode=opts.safe_mode, extras=extras, link_patterns=link_patterns, - use_file_vars=opts.use_file_vars) + use_file_vars=opts.use_file_vars, + cli=True) if py3: sys.stdout.write(html) else: diff --git a/lib/pre_tables.py b/lib/pre_tables.py deleted file mode 100644 index f4f4abc..0000000 --- a/lib/pre_tables.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- encoding: utf-8 -*- - -""" -'pre_tables' transform *html* tables into markdown tables, and put them in some
     tags
    -"""
    -
    -from bs4 import BeautifulSoup
    -
    -def python_table(s_table):
    -    """Transform BeautifulSoup table into list of list"""
    -    rows = []
    -    for row in s_table.find_all('tr'):
    -        # rows.append(list(map( lambda td: td.text, row.find_all(['th', 'td']) )))
    -        rows.append(row.find_all(['th', 'td']))
    -    return rows
    -
    -def pre_table(s_table):
    -    rows = python_table(s_table)
    -    cols_width = [len(cell) for cell in rows[0]]
    -    for j, row in enumerate(rows):
    -        for i, cell in enumerate(row):
    -            if cols_width[i] < len(cell.text):
    -                cols_width[i] = len(cell.text)
    -    text = '
    '
    -    for i, row in enumerate(rows):
    -        if i == 1:
    -            for j, cell in enumerate(row):
    -                text += '|' + '-' * (cols_width[j] + 2)
    -            text += '|\n'
    -
    -        for j, cell in enumerate(row):
    -            text += '| '
    -            if cell.name == 'th':
    -                title = ' ' * ((cols_width[j] - len(cell.text)) // 2) \
    -                        + ''.join(str(node) for node in cell.contents) \
    -                        + ' ' * int(round((cols_width[j] - len(cell.text)) / 2 ) + 1)
    -                # + 1 because of the added space before the closing | of each cell
    -                if cols_width[j] + 1 != len(title):
    -                    title += ' '
    -                text += title
    -            else:
    -                text += ''.join(str(node) for node in cell.contents) \
    -                        + ' ' * (cols_width[j] - len(cell.text) + 1)
    -        text += '|\n'
    -    text += '
    ' - return text - -def pre_tables(html): - soup = BeautifulSoup(html, 'html.parser') - for table in soup.find_all('table'): - table.replace_with(BeautifulSoup(pre_table(table), 'html.parser')) - return str(soup) diff --git a/lib/pygments_from_theme.py b/lib/pygments_from_theme.py deleted file mode 100644 index e2105b1..0000000 --- a/lib/pygments_from_theme.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- encoding: utf-8 -*- - -import os -import sys -from xml.dom.minidom import parseString -from collections import defaultdict - -class Style: - # .highlight is the wrapper class for highlighting therefore - # all css rules are prefixed with .highlight - PREFIX = '.codehilite' - - # ----------------------------------------- - # Params - # name: the name of the class - # args: each argument is an array. - # Each array consists of css properties - # that is either a color or font style - # ---------------------------------------- - - def __init__(self, name, *args): - self.name = name # Name of the class - self.rules = {} # The css rules - for arr in args: - for value in arr: - # Only define properties if they are already not defined - # This allows "cascading" if rules to be applied - if value.startswith('#') and 'color' not in self.rules: - self.rules['color'] = value - else: - if 'italic' in value and 'font-style' not in self.rules: - self.rules['font-style'] = 'italic' - if 'underline' in value and 'text-decoration' not in self.rules: - self.rules['text-decoration'] = 'underline' - if 'bold' in value and 'font-weight' not in self.rules: - self.rules['font-weight'] = 'bold' - - # Helper method for creating the css rule - def _join_attr(self): - temp = [] - if(len(self.rules) == 0): - return '' - for key in self.rules: - temp.append(key + ': ' + self.rules[key]) - return '; '.join(temp) + ';' - - def toString(self): - joined = self._join_attr() - if joined: - return "%s .%s { %s }" % (Style.PREFIX, self.name, joined) - return '' - - -# Crappy xml parsing function for getting the -# colors and font styles from colortheme file - - -def get_settings(color_scheme_content): - settings = defaultdict(lambda: []) - dom = parseString(color_scheme_content) - arr = dom.getElementsByTagName('array')[0] - editor_cfg = arr.getElementsByTagName('dict')[0].getElementsByTagName('dict')[0] - editor_vals = editor_cfg.getElementsByTagName('string') - background = editor_vals[0].firstChild.nodeValue - text_color = editor_vals[2].firstChild.nodeValue - settings['editor_bg'] = background - settings['text_color'] = text_color - for node in arr.childNodes: - if node.nodeName == "dict": - try: - setting = node.getElementsByTagName('string')[1].firstChild.nodeValue - attrs = [] - values = node.getElementsByTagName('dict')[0].getElementsByTagName('string') - for v in values: - if v.firstChild: - a = str(v.firstChild.nodeValue).strip() - attrs.append(a) - for s in setting.split(', '): - settings[s] = attrs - except: - continue - return settings - - -def pygments_from_theme(color_scheme_content): - settings = get_settings(color_scheme_content) - styles = [] - - #Generic - styles.append(Style('ge', ['italic'])) - styles.append(Style('gs', ['bold'])) - - # Comments - styles.append(Style('c', settings['comment'])) - styles.append(Style('cp', settings['comment'])) - styles.append(Style('c1', settings['comment'])) - styles.append(Style('cs', settings['comment'])) - styles.append(Style('cm', settings['comment.block'], settings['comment'])) - - # Constants - styles.append(Style('m', settings['constant.numeric'], settings['constant.other'], settings['constant'], settings['support.constant'])) - styles.append(Style('mf', settings['constant.numeric'], settings['constant.other'], settings['constant'], settings['support.constant'])) - styles.append(Style('mi', settings['constant.numeric'], settings['constant.other'], settings['constant'], settings['support.constant'])) - styles.append(Style('mo', settings['constant.numeric'], settings['constant.other'], settings['constant'], settings['support.constant'])) - styles.append(Style('se', settings['constant.language'], settings['constant.other'], settings['constant'], settings['support.constant'])) - styles.append(Style('kc', settings['constant.language'], settings['constant.other'], settings['constant'], settings['support.constant'])) - - #Keywords - styles.append(Style('k', settings['entity.name.type'], settings['support.type'], settings['keyword'])) - styles.append(Style('kd', settings['storage.type'], settings['storage'])) - styles.append(Style('kn', settings['support.function.construct'], settings['keyword.control'], settings['keyword'])) - styles.append(Style('kt', settings['entity.name.type'], settings['support.type'], settings['support.constant'])) - - #String - styles.append(Style('settings', settings['string.quoted.double'], settings['string.quoted'], settings['string'])) - styles.append(Style('sb', settings['string.quoted.double'], settings['string.quoted'], settings['string'])) - styles.append(Style('sc', settings['string.quoted.single'], settings['string.quoted'], settings['string'])) - styles.append(Style('sd', settings['string.quoted.double'], settings['string.quoted'], settings['string'])) - styles.append(Style('s2', settings['string.quoted.double'], settings['string.quoted'], settings['string'])) - styles.append(Style('sh', settings['string'])) - styles.append(Style('si', settings['string.interpolated'], settings['string'])) - styles.append(Style('sx', settings['string.other'], settings['string'])) - styles.append(Style('sr', settings['string.regexp'], settings['string'])) - styles.append(Style('s1', settings['string.quoted.single'], settings['string'])) - styles.append(Style('ss', settings['string'])) - - #Name - styles.append(Style('na', settings['entity.other.attribute-name'], settings['entity.other'])) - styles.append(Style('bp', settings['variable.language'], settings['variable'])) - styles.append(Style('nc', settings['entity.name.class'], settings['entity.other.inherited-class'], settings['support.class'])) - styles.append(Style('no', settings['constant.language'], settings['constant'])) - styles.append(Style('nd', settings['entity.name.class'])) - styles.append(Style('ne', settings['entity.name.class'])) - styles.append(Style('nf', settings['entity.name.function'], settings['support.function'])) - styles.append(Style('nt', settings['entity.name.tag'], settings['keyword'])) - styles.append(Style('nv', settings['variable'], [settings['text_color']])) - styles.append(Style('vc', settings['variable.language'])) - styles.append(Style('vg', settings['variable.language'])) - styles.append(Style('vi', settings['variable.language'])) - - #Operator - styles.append(Style('ow', settings['keyword.operator'], settings['keyword.operator'], settings['keyword'])) - styles.append(Style('o', settings['keyword.operator'], settings['keyword.operator'], settings['keyword'])) - - # Text - styles.append(Style('n', [settings['text_color']])) - styles.append(Style('nl', [settings['text_color']])) - styles.append(Style('nn', [settings['text_color']])) - styles.append(Style('nx', [settings['text_color']])) - styles.append(Style('bp', settings['variable.language'], settings['variable'], [settings['text_color']])) - styles.append(Style('p', [settings['text_color']])) - - css = '{} {{ background-color: {}; color: {}; }}\n'.format(Style.PREFIX, settings['editor_bg'], settings['text_color']) - for st in styles: - css_style = st.toString() - if css_style: - css += css_style + '\n' - - return css - -if __name__ == "__main__": - args = sys.argv[1:] - if len(args) < 1: - print("Please provide the .tmTheme file!", file=sys.stderr) - sys.exit(1) - - print(pygments_from_theme(args[0])) diff --git a/live-testing/images.md b/live-testing/images.md new file mode 100644 index 0000000..05b7958 --- /dev/null +++ b/live-testing/images.md @@ -0,0 +1,18 @@ +I'm not sure that it **actually** going to work, but it seems nicer than the [previous version][prev] + +this is a test, hello world + +This is the first image from the local file system (absolute path, sorry, it's not going +to work on your system unless your username is math2001): + +![The sublime text logo!](file:///home/math2001/.config/sublime-text-3/Packages/MarkdownLivePreview/live-testing/sublime_text.png) + +This is the first image from the local file system, *relative* path! + +![The sublime text logo!](sublime_merge.png) + +This is the first image from the internet! + +![math2001's logo](https://avatars1.githubusercontent.com/u/15224242?s=400&u=53324cf4e303d15032ba53aa41673a2046b3284b&v=4) + +[prev]: https://github.com/math2001/MarkdownLivePreview/tree/d4c477749ce7e77b8e9fc85464a2488f003c45bc \ No newline at end of file diff --git a/live-testing/sublime_merge.png b/live-testing/sublime_merge.png new file mode 100644 index 0000000..d94427e Binary files /dev/null and b/live-testing/sublime_merge.png differ diff --git a/live-testing/sublime_text.png b/live-testing/sublime_text.png new file mode 100644 index 0000000..8be1c1f Binary files /dev/null and b/live-testing/sublime_text.png differ diff --git a/live-testing/test.md b/live-testing/test.md new file mode 100644 index 0000000..4857206 --- /dev/null +++ b/live-testing/test.md @@ -0,0 +1,18 @@ +# hello world + +This is a *test*. Some inline `[2]code()`. + +what the hell... + +```python +import this + +if input("answer yes") != 'yes': + print("Really?") +``` + +this flickering is really annoying... + +It looks like it's gone... Oh wait nah, it's still here... + +This should still be working, and it is! diff --git a/loading.txt b/loading.txt deleted file mode 100644 index 3bf9bbc..0000000 --- a/loading.txt +++ /dev/null @@ -1 +0,0 @@ -data:image/png;base64,R0lGODlhZABkAPU+AAAAAAwNDRweHyYpKzg8Pzo+QUBFSERJTEdMT05UV1NYXFVbX1hfY1lfZGFobWJpbmhvdGxzeHF5fnJ6gHV9g3Z+hHqDiXuEin+IjoCIjoKLkYKMkoSNk4eQl4iSmIqTmouUm42XnY+ZoJKco5OdpJOepZSeppahqJeiqZmjqpumrZ6psJ+qsqOutqSvt6SwuKezu6i0vKm1vay4wK66wq+7w6+8xLK+xrK/x7TAybXCy7bDy7jEzbjFzrzJ0gAAACH5BAUAAD4AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAZABkAAAG/kCfcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiW8IAAAUilUpjQABkEsmMUchkwBIOTOQBQICGUabk0ctFhYdiSajAgOZRKeNRjkYqxaghyuwAgxFtZ1FJBe6NokHvya0nEUzuhYgijG/B86oRCDSOZAPv6VCw0SquiiWNwOwAzfjz0I8uasYPIMvDQ0kRhm/Ee/afKiQ1sIIDBAgkuUxQKDhA29ERMHK9GJSJR85pLUiwkOELgx6Goo0sG/IK1gVhCig9MjHimOreAmBMU+XngciRTrAMSQB/qxmR6KtEjGko7Shey7kbGgA6A0GBz4oOUjCno8YNXWp6NOCwVICD6UYPQqiBiANDHNOkILiqIYVg2Y0yPlAikddICASQtuwJJQY9OAimqFCZpRPei0pPnKjg4fHkB936JBYyg4VmDNrVlH5zYMFoEOLZgDBSoejqDfQEc1atBXUsOl8bi26bpUNsKWpnlPjg+PIj32brZJjs/HOi5PjiMFzCo4ZyAWpqCBhwgspMFa9NZRjg4TvEjZCEQFzWvQ9KiiA/+73SVtpGAT7mcFh/XcPVqH0uCsNhDs+J9gnAQXX+cADCSDMggRVVtGE2lZ6fCAgfkPcdYFhRAhlAVHxxfCnC4d42EdghtII1hYGLgjxki6GOSiNHtR990F+QpymizcZ0SNEjquI1+FHetDHQYFEuCANhBpaMMRAuqRYxEEJDSLPR1YlWVRN9Vjy3ioFCWHlEC6Uh44iOcB0gQck2kSEB90o4sEFx1yY5irQ9JdIDdIANcSXRBiDzGAfVcbnELiwmEgHx3Q5p5JGmOPjIdAF9eIRnyRnhA1AWvqEn4pq6umnoIYq6qiklmrqqaimquqqrLbq6quwxirrrLTWauuttwYBADs= \ No newline at end of file diff --git a/markdown2html.py b/markdown2html.py new file mode 100644 index 0000000..eed7dbb --- /dev/null +++ b/markdown2html.py @@ -0,0 +1,119 @@ +import copy +import os.path +import concurrent.futures +import urllib.request +import base64 +import bs4 + +from functools import lru_cache, partial + +from .lib.markdown2 import Markdown + +__all__ = ('markdown2html', ) + +markdowner = Markdown(extras=['fenced-code-blocks']) + +# FIXME: how do I choose how many workers I want? Does thread pool reuse threads or +# does it stupidly throw them out? (we could implement something of our own) +executor = concurrent.futures.ThreadPoolExecutor(max_workers=5) + +images_cache = {} + +class LoadingError(Exception): + pass + +def markdown2html(markdown, basepath, re_render, resources): + """ converts the markdown to html, loads the images and puts in base64 for sublime + to understand them correctly. That means that we are responsible for loading the + images from the internet. Hence, we take in re_render, which is just a function we + call when an image has finished loading to retrigger a render (see #90) + """ + html = markdowner.convert(markdown) + + soup = bs4.BeautifulSoup(html, "html.parser") + for img_element in soup.find_all('img'): + src = img_element['src'] + + # already in base64, or something of the like + # FIXME: what other types are possible? Are they handled by ST? If not, could we + # convert it into base64? is it worth the effort? + if src.startswith('data:image/'): + continue + + if src.startswith('http://') or src.startswith('https://'): + path = src + elif src.startswith('file://'): + path = src[len('file://'):] + else: + # expanduser: ~ -> /home/math2001 + # realpath: simplify that paths so that we don't have duplicated caches + path = os.path.realpath(os.path.expanduser(os.path.join(basepath, src))) + + try: + base64 = get_base64_image(path, re_render) + except FileNotFoundError as e: + base64 = resources['base64_404_image'] + except LoadingError: + base64 = resources['base64_loading_image'] + + img_element['src'] = base64 + + # remove comments, because they pollute the console with error messages + for comment_element in soup.find_all(text=lambda text: isinstance(text, bs4.Comment)): + comment_element.extract() + + # FIXME: how do tables look? should we use ascii tables? + + # pre aren't handled by ST3. The require manual adjustment + for pre_element in soup.find_all('pre'): + # select the first child, + code_element = next(pre_element.children) + + # FIXME: this method sucks, but can we do better? + fixed_pre = str(code_element) \ + .replace(' ', '.') \ + .replace('\n', '
    ') + + code_element.replace_with(bs4.BeautifulSoup(fixed_pre, "html.parser")) + + # FIXME: highlight the code using Sublime's syntax + + # FIXME: report that ST doesn't support
    but does work with
    ... WTF? + return "\n\n{}".format(resources['stylesheet'], soup).replace('
    ', '
    ') + +def get_base64_image(path, re_render): + + def callback(url, future): + # this is "safe" to do because callback is called in the same thread as + # add_done_callback: + # > Added callables are called in the order that they were added and are always + # > called in a thread belonging to the process that added them + # > --- Python docs + images_cache[url] = future.result() + # we render, which means this function will be called again, but this time, we + # will read from the cache + re_render() + + if path.startswith('http://') or path.startswith('https://'): + if path in images_cache: + return images_cache[path] + executor.submit(load_image, path).add_done_callback(partial(callback, path)) + raise LoadingError() + + # FIXME: use some kind of cache for this as well, because it decodes on every + # keystroke here... + with open(path, 'rb') as fp: + return 'data:image/png;base64,' + base64.b64encode(fp.read()).decode('utf-8') + +# FIXME: wait what the hell? Why do I have two caches? (lru and images_cache) +# FIXME: This is an in memory cache. 20 seems like a fair bit of images... Should it be +# bigger? Should the user be allowed to chose? There definitely should be a limit +# because we don't wanna use to much memory, we're a simple markdown preview plugin +# NOTE: > The LRU feature performs best when maxsize is a power-of-two. --- python docs +@lru_cache(maxsize=2 ** 4) +def load_image(url): + with urllib.request.urlopen(url, timeout=60) as conn: + content_type = conn.info().get_content_type() + if 'image' not in content_type: + raise ValueError("{!r} doesn't point to an image, but to a {!r}".format(url, content_type)) + return 'data:image/png;base64,' + base64.b64encode(conn.read()).decode('utf-8') diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index bd1b174..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,35 +0,0 @@ -site_name: MarkdownLivePreview -theme: material -repo_name: math2001/MarkdownLivePreview -repo_url: https://github.com/math2001/MarkdownLivePreview -site_description: Sublime Text 3 Plugin MarkdownLivePreview's documentation -site_author: math2001 - -markdown_extensions: - - toc(permalink=true) - - pymdownx.arithmatex - - pymdownx.betterem(smart_enable=all) - - pymdownx.caret - - pymdownx.critic - - pymdownx.emoji: - emoji_generator: !!python/name:pymdownx.emoji.to_svg - - pymdownx.inlinehilite - - pymdownx.magiclink - - pymdownx.mark - - pymdownx.smartsymbols - - pymdownx.superfences - - pymdownx.tasklist(custom_checkbox=true) - - pymdownx.tilde - - admonition - - codehilite - -extra: - logo: imgs/MarkdownLivePreview-opposite.svg - palette: - primary: Blue - accent: Indigo - social: - - type: github - link: https://github.com/math2001 - - type: twitter - link: https://twitter.com/_math2001 diff --git a/resources/404.base64 b/resources/404.base64 new file mode 100644 index 0000000..d82c610 --- /dev/null +++ b/resources/404.base64 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/imgs/404-image.png b/resources/404.png similarity index 100% rename from imgs/404-image.png rename to resources/404.png diff --git a/resources/convertresources.py b/resources/convertresources.py new file mode 100644 index 0000000..fdcd878 --- /dev/null +++ b/resources/convertresources.py @@ -0,0 +1,9 @@ +""" A small script to convert the images into base64 data """ + +from base64 import b64encode + +with open('404.png', 'rb') as png, open('404.base64', 'wb') as base64: + base64.write(b64encode(png.read())) + +with open('loading.png', 'rb') as png, open('loading.base64', 'wb') as base64: + base64.write(b64encode(png.read())) diff --git a/resources/loading.base64 b/resources/loading.base64 new file mode 100644 index 0000000..3fcbe3d --- /dev/null +++ b/resources/loading.base64 @@ -0,0 +1 @@ +R0lGODlhZABkAPU+AAAAAAwNDRweHyYpKzg8Pzo+QUBFSERJTEdMT05UV1NYXFVbX1hfY1lfZGFobWJpbmhvdGxzeHF5fnJ6gHV9g3Z+hHqDiXuEin+IjoCIjoKLkYKMkoSNk4eQl4iSmIqTmouUm42XnY+ZoJKco5OdpJOepZSeppahqJeiqZmjqpumrZ6psJ+qsqOutqSvt6SwuKezu6i0vKm1vay4wK66wq+7w6+8xLK+xrK/x7TAybXCy7bDy7jEzbjFzrzJ0gAAACH5BAUAAD4AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAZABkAAAG/kCfcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiW8IAAAUilUpjQABkEsmMUchkwBIOTOQBQICGUabk0ctFhYdiSajAgOZRKeNRjkYqxaghyuwAgxFtZ1FJBe6NokHvya0nEUzuhYgijG/B86oRCDSOZAPv6VCw0SquiiWNwOwAzfjz0I8uasYPIMvDQ0kRhm/Ee/afKiQ1sIIDBAgkuUxQKDhA29ERMHK9GJSJR85pLUiwkOELgx6Goo0sG/IK1gVhCig9MjHimOreAmBMU+XngciRTrAMSQB/qxmR6KtEjGko7Shey7kbGgA6A0GBz4oOUjCno8YNXWp6NOCwVICD6UYPQqiBiANDHNOkILiqIYVg2Y0yPlAikddICASQtuwJJQY9OAimqFCZpRPei0pPnKjg4fHkB936JBYyg4VmDNrVlH5zYMFoEOLZgDBSoejqDfQEc1atBXUsOl8bi26bpUNsKWpnlPjg+PIj32brZJjs/HOi5PjiMFzCo4ZyAWpqCBhwgspMFa9NZRjg4TvEjZCEQFzWvQ9KiiA/+73SVtpGAT7mcFh/XcPVqH0uCsNhDs+J9gnAQXX+cADCSDMggRVVtGE2lZ6fCAgfkPcdYFhRAhlAVHxxfCnC4d42EdghtII1hYGLgjxki6GOSiNHtR990F+QpymizcZ0SNEjquI1+FHetDHQYFEuCANhBpaMMRAuqRYxEEJDSLPR1YlWVRN9Vjy3ioFCWHlEC6Uh44iOcB0gQck2kSEB90o4sEFx1yY5irQ9JdIDdIANcSXRBiDzGAfVcbnELiwmEgHx3Q5p5JGmOPjIdAF9eIRnyRnhA1AWvqEn4pq6umnoIYq6qiklmrqqaimquqqrLbq6quwxirrrLTWauuttwYBADs= \ No newline at end of file diff --git a/imgs/loading.png b/resources/loading.png similarity index 100% rename from imgs/loading.png rename to resources/loading.png diff --git a/resources/stylesheet.css b/resources/stylesheet.css new file mode 100644 index 0000000..fb94801 --- /dev/null +++ b/resources/stylesheet.css @@ -0,0 +1,44 @@ +html { + --light-bg: color(var(--background) blend(#fff 90%)); + --very-light-bg: color(var(--background) blend(#fff 85%)); +} + +body { + font-family: "Ubuntu", "DejaVu Sans", "Open Sans", sans-serif; + font-size: 15px; +} + +blockquote { + font-style: italic; + display: block; + margin-left: 30px; +} + +pre { + display: block; + background-color: var(--very-light-bg); +} + +code { + padding-left: 0.2rem; + padding-right: 0.2rem; + margin: 0; + border-radius: 3px; + background-color: red; +} + +code { + padding-left: 0.2rem; + padding-right: 0.2rem; + background-color: var(--very-light-bg); + margin: 0; + border-radius: 3px; +} + +pre code { + padding: 0; +} + +i.space { + color: var(--very-light-bg); +} \ No newline at end of file diff --git a/setting_names.py b/setting_names.py deleted file mode 100644 index b035dcf..0000000 --- a/setting_names.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- encoding: utf-8 -*- - -PREVIEW_ENABLED = 'markdown_live_preview_enabled' -PREVIEW_ID = 'markdown_live_preview_id' -IS_PREVIEW = 'is_markdown_live_preview' -IS_HIDDEN = 'is_hidden_markdown_live_preview' -MD_VIEW_ID = 'markdown_live_preview_md_id' -PREVIEW_WINDOW = 'markdown_live_preview_window' -ON_OPEN = 'markdown_live_preview_on_open' -LAST_UPDATE = 'markdonw_live_preview_last_run' diff --git a/test.md b/test.md deleted file mode 100644 index eb42173..0000000 --- a/test.md +++ /dev/null @@ -1,16 +0,0 @@ -```python -import this - -if you.are('new'): - print('Welcome!') - if you.are('brand new'): - print("You'll see, python's just awesome") -else: - print('Hello!') -``` - -![img](docs/imgs/syntax-specific-settings.png) - -
      -
    1. test
    2. -
    \ No newline at end of file diff --git a/todo b/todo deleted file mode 100644 index e569858..0000000 --- a/todo +++ /dev/null @@ -1,38 +0,0 @@ -Fast: - ☐ cache image in object when used, so that it's faster @needsTest - -Medium: - ☐ auto refresh preview if loading images - ☐ use alt attribute for 404 error - ☐ optimize usage of BeautifulSoup - -Long: - ☐ support anchor (TOC) @big - -Unknown: - ☐ properly convert tmtheme to css - -___________________ -Archive: - ✔ add settings to keep md view open #13 @done Sat 11 Feb 2017 at 09:10 @project(Fast) - ✔ fix custom css @bug @done Sun 22 Jan 2017 at 18:40 @project(Medium) - ✘ check how many times is the show_html function called @cancelled Sun 22 Jan 2017 at 18:40 @project(Unknown) - ✔ sync scroll @needsUpdate(because of images) @done Sun 22 Jan 2017 at 18:39 @project(Fast) - ✔ fix #4 @high @done Mon 09 Jan 2017 at 18:42 @project(Long) - ✔ use MarkdownLivePreview syntax, so we can use syntax's settings @done Mon 09 Jan 2017 at 18:41 @project(Medium) - ✔ add clear cache command @done Mon 09 Jan 2017 at 18:41 @project(Fast) - ✔ update README for settings in view @done Mon 09 Jan 2017 at 18:41 @project(Fast) - ✔ add edit settings @done Mon 09 Jan 2017 at 18:41 @project(Fast) - ✘ listen for settings to change @cancelled Mon 09 Jan 2017 at 18:41 @project(Medium) - ✘ call settings listener on_new too - might be too heavy @cancelled Sun 08 Jan 2017 at 19:33 @project(Fast) - ✔ fix relative source @done Sun 08 Jan 2017 at 19:22 @project(Medium) - ✔ add settings for the preview @done Sun 08 Jan 2017 at 17:36 @project(Fast) - ✔ regive focus to the right markdown view @done Mon 02 Jan 2017 at 18:34 @project(Fast) - ✔ try/except for 404 @done Mon 02 Jan 2017 at 18:03 @project(Fast) - ✔ fix bug when empty `src` @done Mon 02 Jan 2017 at 17:15 @project(Fast) - ✔ preview.set_scratch(True) @done Mon 02 Jan 2017 at 16:58 - ✔ set the title of the preview @done Mon 02 Jan 2017 at 16:58 - ✔ preview.wordWrap => True @done Mon 02 Jan 2017 at 16:58 - ✔ clean the code (syntax) @done Mon 02 Jan 2017 at 16:58 - ✔ add 404 image @done Mon 02 Jan 2017 at 16:57 - ✔ load images from internet (`https:`) @done Mon 02 Jan 2017 at 16:57 diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..e0661d1 --- /dev/null +++ b/utils.py @@ -0,0 +1,23 @@ +# import sublime +import time + +def get_settings(): + return sublime.get_settings("MarkdownLivePreview.sublime-settings") + +def min_time_between_call(timeout, on_block=lambda *args, **kwargs: None): + """ Enforces a timeout between each call to the function + timeout is in seconds + """ + last_call = 0 + + def outer(func): + def wrapper(*args, **kwargs): + nonlocal last_call + + if time.time() - last_call < timeout: + time.sleep(timeout - (time.time() - last_call)) + + last_call = time.time() + return func(*args, **kwargs) + return wrapper + return outer