diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap index 2d42b0a5..94d4e34f 100644 --- a/Default (Linux).sublime-keymap +++ b/Default (Linux).sublime-keymap @@ -66,6 +66,14 @@ { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } ] }, + { "keys": ["tab"], "command": "indent_list_multiitem", "context": + [ + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "^\\s*(>\\s*)?[*+\\-]\\s+(.*)$", "match_all": true }, + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } + ] + }, { "keys": ["shift+tab"], "command": "indent_list_item", "args": {"reverse": true}, "context": [ { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, @@ -75,6 +83,14 @@ { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } ] }, + { "keys": ["shift+tab"], "command": "indent_list_multiitem", "args": {"reverse": true}, "context": + [ + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "^\\s*(>\\s*)?[*+\\-]\\s+(.*)$", "match_all": true }, + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } + ] + }, { "keys": ["_"], "command": "insert_snippet", "args": {"contents": "_$0_"}, "context": [ { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, @@ -636,5 +652,10 @@ { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, { "key": "preceding_text", "operator": "regex_contains", "operand": "(> )+", "match_all": true } ] + }, + { "keys": ["ctrl+shift+m"], "command": "lint", "context": + [ + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true } + ] } ] diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap index 311ebcd3..f5ee455c 100644 --- a/Default (OSX).sublime-keymap +++ b/Default (OSX).sublime-keymap @@ -66,6 +66,14 @@ { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } ] }, + { "keys": ["tab"], "command": "indent_list_multiitem", "context": + [ + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "^\\s*(>\\s*)?[*+\\-]\\s+(.*)$", "match_all": true }, + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } + ] + }, { "keys": ["shift+tab"], "command": "indent_list_item", "args": {"reverse": true}, "context": [ { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, @@ -75,6 +83,14 @@ { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } ] }, + { "keys": ["shift+tab"], "command": "indent_list_multiitem", "args": {"reverse": true}, "context": + [ + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "^\\s*(>\\s*)?[*+\\-]\\s+(.*)$", "match_all": true }, + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } + ] + }, { "keys": ["_"], "command": "insert_snippet", "args": {"contents": "_$0_"}, "context": [ { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, @@ -636,5 +652,10 @@ { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, { "key": "preceding_text", "operator": "regex_contains", "operand": "(> )+", "match_all": true } ] + }, + { "keys": ["super+shift+m"], "command": "lint", "context": + [ + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true } + ] } ] diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap index 2d42b0a5..94d4e34f 100644 --- a/Default (Windows).sublime-keymap +++ b/Default (Windows).sublime-keymap @@ -66,6 +66,14 @@ { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } ] }, + { "keys": ["tab"], "command": "indent_list_multiitem", "context": + [ + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "^\\s*(>\\s*)?[*+\\-]\\s+(.*)$", "match_all": true }, + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } + ] + }, { "keys": ["shift+tab"], "command": "indent_list_item", "args": {"reverse": true}, "context": [ { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, @@ -75,6 +83,14 @@ { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } ] }, + { "keys": ["shift+tab"], "command": "indent_list_multiitem", "args": {"reverse": true}, "context": + [ + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "^\\s*(>\\s*)?[*+\\-]\\s+(.*)$", "match_all": true }, + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "markup.raw", "match_all": true } + ] + }, { "keys": ["_"], "command": "insert_snippet", "args": {"contents": "_$0_"}, "context": [ { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, @@ -636,5 +652,10 @@ { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }, { "key": "preceding_text", "operator": "regex_contains", "operand": "(> )+", "match_all": true } ] + }, + { "keys": ["ctrl+shift+m"], "command": "lint", "context": + [ + { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true } + ] } ] diff --git a/Default.sublime-commands b/Default.sublime-commands index 3de5cda6..64566601 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -24,5 +24,9 @@ { "file": "${packages}/User/Markdown.sublime-settings" } + }, + { + "caption": "MarkdownEditing: Markdown Lint", + "command": "lint" } ] diff --git a/Main.sublime-menu b/Main.sublime-menu index 7b7be4c8..c6586934 100644 --- a/Main.sublime-menu +++ b/Main.sublime-menu @@ -1,74 +1,150 @@ [ { "caption": "Preferences", - "mnemonic": "n", - "id": "preferences", - "children": - [ + "children": [ { "caption": "Package Settings", - "mnemonic": "P", - "id": "package-settings", - "children": - [ + "children": [ { "caption": "Markdown Editing", - "children": - [ + "children": [ + { + "args": { + "file": "${packages}/MarkdownEditing/README.md" + }, + "caption": "README", + "command": "open_file" + }, + { + "caption": "-" + }, + { + "args": { + "file": "${packages}/MarkdownEditing/Bold and Italic Markers.tmPreferences" + }, + "caption": "Bold and Italic Settings \u2013 Default", + "command": "open_file" + }, + { + "args": { + "file": "${packages}/User/Bold and Italic Markers.tmPreferences" + }, + "caption": "Bold and Italic Settings \u2013 User", + "command": "open_file" + }, + { + "caption": "-" + }, + { + "args": { + "file": "${packages}/MarkdownEditing/Markdown.sublime-settings" + }, + "caption": "Markdown GFM Settings \u2013 Default", + "command": "open_file" + }, { - "command": "open_file", - "args": {"file": "${packages}/MarkdownEditing/README.md"}, - "caption": "README" + "args": { + "file": "${packages}/User/Markdown.sublime-settings" + }, + "caption": "Markdown GFM Settings \u2013 User", + "command": "open_file" }, - { "caption": "-" }, { - "command": "open_file", - "args": {"file": "${packages}/MarkdownEditing/Bold and Italic Markers.tmPreferences"}, - "caption": "Bold and Italic Settings – Default" + "caption": "-" }, { - "command": "open_file", - "args": {"file": "${packages}/User/Bold and Italic Markers.tmPreferences"}, - "caption": "Bold and Italic Settings – User" + "args": { + "file": "${packages}/MarkdownEditing/Markdown (Standard).sublime-settings" + }, + "caption": "Markdown (Standard) Settings \u2013 Default", + "command": "open_file" }, - { "caption": "-" }, { - "command": "open_file", - "args": {"file": "${packages}/MarkdownEditing/Markdown.sublime-settings"}, - "caption": "Markdown GFM Settings – Default" + "args": { + "file": "${packages}/User/Markdown (Standard).sublime-settings" + }, + "caption": "Markdown (Standard) Settings \u2013 User", + "command": "open_file" }, { - "command": "open_file", - "args": {"file": "${packages}/User/Markdown.sublime-settings"}, - "caption": "Markdown GFM Settings – User" + "caption": "-" }, - { "caption": "-" }, { - "command": "open_file", - "args": {"file": "${packages}/MarkdownEditing/Markdown (Standard).sublime-settings"}, - "caption": "Markdown (Standard) Settings – Default" + "args": { + "file": "${packages}/MarkdownEditing/MultiMarkdown.sublime-settings" + }, + "caption": "MultiMarkdown Settings \u2013 Default", + "command": "open_file" }, { - "command": "open_file", - "args": {"file": "${packages}/User/Markdown (Standard).sublime-settings"}, - "caption": "Markdown (Standard) Settings – User" + "args": { + "file": "${packages}/User/MultiMarkdown.sublime-settings" + }, + "caption": "MultiMarkdown Settings \u2013 User", + "command": "open_file" }, - { "caption": "-" }, { - "command": "open_file", - "args": {"file": "${packages}/MarkdownEditing/MultiMarkdown.sublime-settings"}, - "caption": "MultiMarkdown Settings – Default" + "caption": "-" }, { - "command": "open_file", - "args": {"file": "${packages}/User/MultiMarkdown.sublime-settings"}, - "caption": "MultiMarkdown Settings – User" + "args": { + "file": "${packages}/MarkdownEditing/Default (Windows).sublime-keymap", + "platform": "Windows" + }, + "caption": "Key Bindings \u2013 Default", + "command": "open_file" }, - { "caption": "-" } + { + "args": { + "file": "${packages}/MarkdownEditing/Default (OSX).sublime-keymap", + "platform": "OSX" + }, + "caption": "Key Bindings \u2013 Default", + "command": "open_file" + }, + { + "args": { + "file": "${packages}/MarkdownEditing/Default (Linux).sublime-keymap", + "platform": "Linux" + }, + "caption": "Key Bindings \u2013 Default", + "command": "open_file" + }, + { + "args": { + "file": "${packages}/User/Default (Windows).sublime-keymap", + "platform": "Windows" + }, + "caption": "Key Bindings \u2013 User", + "command": "open_file" + }, + { + "args": { + "file": "${packages}/User/Default (OSX).sublime-keymap", + "platform": "OSX" + }, + "caption": "Key Bindings \u2013 User", + "command": "open_file" + }, + { + "args": { + "file": "${packages}/User/Default (Linux).sublime-keymap", + "platform": "Linux" + }, + "caption": "Key Bindings \u2013 User", + "command": "open_file" + }, + { + "caption": "-" + } ] } - ] + ], + "id": "package-settings", + "mnemonic": "P" } - ] + ], + "id": "preferences", + "mnemonic": "n" } -] +] \ No newline at end of file diff --git a/Markdown (Standard).sublime-settings b/Markdown (Standard).sublime-settings index 27578d57..01b5404c 100644 --- a/Markdown (Standard).sublime-settings +++ b/Markdown (Standard).sublime-settings @@ -39,12 +39,44 @@ // List bullets to be used for automatically switching. In their order. "mde.list_indent_bullets": ["*", "-", "+"], - // Allways keep current line vertically centered. + // Always keep current line vertically centered. "mde.keep_centered": false, - // Distraction free mode improvements. In order these to work, you have to install + // Distraction free mode improvements. In order for these to work, you have to install // FullScreenStatus plugin: https://github.com/maliayas/SublimeText_FullScreenStatus "mde.distraction_free_mode": { "mde.keep_centered": true + }, + + "lint":{ + // disabled rules, e.g. "md001". + "disable": ["md013"], + // Options: + // atx, ## title only + // atx_closed, ## title ## only + // setext, title only + // ===== + // any, consistent within the document + "md003": "any", + // Options: + // asterisk, * only + // plus, + only + // dash, - only + // cyclic, different symbols on different levels + // and same symbol on same level + // single, same symbol on different levels + // any, same symbol on same level + "md004": "cyclic", + // Number of spaces per list indent. Set to 0 to use Sublime tab_size instead + "md007": 0, + // Maximum line length, Set to 0 to use Sublime wrap_width instead + "md013": 0, + // Disallowed trailing punctuation in headers + "md026": ".,;:!", + // Options: + // one, '1.' only + // ordered, ascending number + // any, consistent within one list + "md029": "any" } } diff --git a/Markdown.sublime-settings b/Markdown.sublime-settings index 275a067c..5f9a74de 100644 --- a/Markdown.sublime-settings +++ b/Markdown.sublime-settings @@ -50,12 +50,44 @@ // List bullets to be used for automatically switching. In their order. "mde.list_indent_bullets": ["*", "-", "+"], - // Allways keep current line vertically centered. + // Always keep current line vertically centered. "mde.keep_centered": false, - // Distraction free mode improvements. In order these to work, you have to install + // Distraction free mode improvements. In order for these to work, you have to install // FullScreenStatus plugin: https://github.com/maliayas/SublimeText_FullScreenStatus "mde.distraction_free_mode": { "mde.keep_centered": true + }, + + "lint":{ + // disabled rules, e.g. "md001". + "disable": ["md013"], + // Options: + // atx, ## title only + // atx_closed, ## title ## only + // setext, title only + // ===== + // any, consistent within the document + "md003": "any", + // Options: + // asterisk, * only + // plus, + only + // dash, - only + // cyclic, different symbols on different levels + // and same symbol on same level + // single, same symbol on different levels + // any, same symbol on same level + "md004": "cyclic", + // Number of spaces per list indent. Set to 0 to use Sublime tab_size instead + "md007": 0, + // Maximum line length, Set to 0 to use Sublime wrap_width instead + "md013": 0, + // Disallowed trailing punctuation in headers + "md026": ".,;:!", + // Options: + // one, '1.' only + // ordered, ascending number + // any, consistent within one list + "md029": "any" } } diff --git a/Markdown.tmLanguage b/Markdown.tmLanguage index d6568315..ae0ea8ba 100644 --- a/Markdown.tmLanguage +++ b/Markdown.tmLanguage @@ -99,15 +99,10 @@ begin - ^[ ]{0,3}([0-9]+)(\.)(?=\s) + ^[ ]{0,3}([0-9]+\.)(?=\s) captures 1 - - name - punctuation.definition.list_item.markdown punctuation.definition.list_item.number.markdown - - 2 name punctuation.definition.list_item.markdown @@ -610,7 +605,7 @@ end - (?<=\S)(\1) + (?<=\S)(\1)(?!\w) name markup.strikethrough.markdown patterns @@ -1329,7 +1324,7 @@ 1 name - punctuation.definition.list_item + punctuation.definition.list_item.markdown @@ -1380,7 +1375,7 @@ fenced-html begin - ^(\s*[`~]{3,})(html|html5)\s*$ + ^(\s*[`~]{3,})\s*(html|html5)\s*$ end ^(\1)\n name @@ -1396,7 +1391,7 @@ fenced-xml begin - ^(\s*[`~]{3,})(xml)\s*$ + ^(\s*[`~]{3,})\s*(xml)\s*$ end ^(\1)\n name @@ -1412,7 +1407,7 @@ fenced-diff begin - ^(\s*[`~]{3,})(diff|patch)\s*$ + ^(\s*[`~]{3,})\s*(diff|patch)\s*$ end ^(\1)\n name @@ -1428,7 +1423,7 @@ fenced-perl begin - ^(\s*[`~]{3,})(perl)\s*$ + ^(\s*[`~]{3,})\s*(perl)\s*$ end ^(\1)\n name @@ -1444,7 +1439,7 @@ fenced-php begin - ^(\s*[`~]{3,})(php)\s*$ + ^(\s*[`~]{3,})\s*(php)\s*$ end ^(\1)\n name @@ -1460,7 +1455,7 @@ fenced-css begin - ^(\s*[`~]{3,})(css)\s*$ + ^(\s*[`~]{3,})\s*(css)\s*$ end ^(\1)\n name @@ -1476,7 +1471,7 @@ fenced-less begin - ^(\s*[`~]{3,})(less)\s*$ + ^(\s*[`~]{3,})\s*(less)\s*$ end ^(\1)\n name @@ -1492,7 +1487,7 @@ fenced-java begin - ^(\s*[`~]{3,})(java)\s*$ + ^(\s*[`~]{3,})\s*(java)\s*$ end ^(\1)\n name @@ -1508,7 +1503,7 @@ fenced-c begin - ^(\s*[`~]{3,})(c)\s*$ + ^(\s*[`~]{3,})\s*(c)\s*$ end ^(\1)\n name @@ -1524,7 +1519,7 @@ fenced-c++ begin - ^(\s*[`~]{3,})(c\+\+|cpp)\s*$ + ^(\s*[`~]{3,})\s*(c\+\+|cpp)\s*$ end ^(\1)\n name @@ -1540,7 +1535,7 @@ fenced-c# begin - ^(\s*[`~]{3,})(c(?:s|sharp|#))\s*$ + ^(\s*[`~]{3,})\s*(c(?:s|sharp|#))\s*$ end ^(\1)\n name @@ -1556,7 +1551,7 @@ fenced-yaml begin - ^(\s*[`~]{3,})(yaml)\s*$ + ^(\s*[`~]{3,})\s*(yaml)\s*$ end ^(\1)\n name @@ -1572,7 +1567,7 @@ fenced-sql begin - ^(\s*[`~]{3,})(sql)\s*$ + ^(\s*[`~]{3,})\s*(sql)\s*$ end ^(\1)\n name @@ -1588,7 +1583,7 @@ fenced-shell begin - ^(\s*[`~]{3,})(sh|shell)\s*$ + ^(\s*[`~]{3,})\s*(sh|shell|bash)\s*$ end ^(\1)\n name @@ -1604,7 +1599,7 @@ fenced-sass begin - ^(\s*[`~]{3,})(sass|scss)\s*$ + ^(\s*[`~]{3,})\s*(sass|scss)\s*$ end ^(\1)\n name @@ -1620,7 +1615,7 @@ fenced-scala begin - ^(\s*[`~]{3,})(scala)\s*$ + ^(\s*[`~]{3,})\s*(scala)\s*$ end ^(\1)\n name @@ -1636,7 +1631,7 @@ fenced-obj-c begin - ^(\s*[`~]{3,})(objective-c)\s*$ + ^(\s*[`~]{3,})\s*(objective-c)\s*$ end ^(\1)\n name @@ -1652,7 +1647,7 @@ fenced-coffee begin - ^(\s*[`~]{3,})(coffee)\s*$ + ^(\s*[`~]{3,})\s*(coffee)\s*$ end ^(\1)\n name @@ -1668,7 +1663,7 @@ fenced-js begin - ^(\s*[`~]{3,})(js|json|javascript)\s*$ + ^(\s*[`~]{3,})\s*(js|json|javascript)\s*$ end ^(\1)\n name @@ -1684,7 +1679,7 @@ fenced-ruby begin - ^(\s*[`~]{3,})(ruby)\s*$ + ^(\s*[`~]{3,})\s*(ruby)\s*$ end ^(\1)\n name @@ -1700,7 +1695,7 @@ fenced-python begin - ^(\s*[`~]{3,})(py|python)\s*$ + ^(\s*[`~]{3,})\s*(py|python)\s*$ end ^(\1)\n name @@ -1716,7 +1711,7 @@ fenced-lisp begin - ^(\s*[`~]{3,})lisp\s*$ + ^(\s*[`~]{3,})\s*(lisp)\s*$ end ^(\1)\n name @@ -1729,10 +1724,26 @@ + fenced-lua + + begin + ^(\s*[`~]{3,})\s*(lua)\s*$ + end + ^(\1)\n + name + markup.raw.block.markdown markup.raw.block.fenced.markdown + patterns + + + include + source.lua + + + fenced-undefined begin - ^(\s*(`{3,}|~{3,}))(.*?)\s*$ + ^(\s*(`{3,}|~{3,}))\s*[\w\-+#]*\s*$ end ^(\1)\n name @@ -1830,6 +1841,10 @@ include #fenced-lisp + + include + #fenced-lua + include #fenced-undefined diff --git a/MarkdownEditor-Dark.tmTheme b/MarkdownEditor-Dark.tmTheme index f2f23ffb..42e6a547 100644 --- a/MarkdownEditor-Dark.tmTheme +++ b/MarkdownEditor-Dark.tmTheme @@ -430,9 +430,14 @@ name - Markdown: Punctuation + Markdown: Lists scope - markup.list.unnumbered.markdown meta.paragraph.list.markdown, markup.list.unnumbered.markdown punctuation.definition.list_item.markdown, markup.list.numbered.markdown, markup.list.numbered.markdown meta.paragraph.list.markdown, markup.list.numbered.markdown punctuation.definition.list_item.markdown + + , markup.list.unnumbered.markdown, + , markup.list.unnumbered.markdown meta.paragraph.list.markdown, + , markup.list.numbered.markdown, + , markup.list.numbered.markdown meta.paragraph.list.markdown, + settings foreground @@ -441,6 +446,20 @@ #11111100 + + name + Markdown: Lists + scope + + , markup.list.unnumbered.markdown punctuation.definition.list_item.markdown, + , markup.list.numbered.markdown punctuation.definition.list_item.markdown, + + settings + + foreground + #ffffff + + name Markup: Output @@ -811,6 +830,62 @@ #FF420077 + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #F92672 + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #A6E22E + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #967EFB + + + + name + GitGutter ignored + scope + markup.ignored.git_gutter + settings + + foreground + #565656 + + + + name + GitGutter untracked + scope + markup.untracked.git_gutter + settings + + foreground + #565656 + + uuid BF4E1964-0DB9-4E88-8142-E8F52D7EDEEC diff --git a/MarkdownEditor-Focus.tmTheme b/MarkdownEditor-Focus.tmTheme index fd9c13c9..723a85ab 100644 --- a/MarkdownEditor-Focus.tmTheme +++ b/MarkdownEditor-Focus.tmTheme @@ -808,6 +808,62 @@ #d5f6ff88 + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #F92672 + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #A6E22E + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #967EFB + + + + name + GitGutter ignored + scope + markup.ignored.git_gutter + settings + + foreground + #565656 + + + + name + GitGutter untracked + scope + markup.untracked.git_gutter + settings + + foreground + #565656 + + uuid BF4E1964-0DB9-4E88-8142-E8F52D7EDEEC diff --git a/MarkdownEditor-Yellow.tmTheme b/MarkdownEditor-Yellow.tmTheme index 87d444ed..4cafc4be 100644 --- a/MarkdownEditor-Yellow.tmTheme +++ b/MarkdownEditor-Yellow.tmTheme @@ -430,9 +430,14 @@ name - Markdown: Punctuation + Markdown: Lists scope - markup.list.unnumbered.markdown meta.paragraph.list.markdown, markup.list.unnumbered.markdown punctuation.definition.list_item.markdown, markup.list.numbered.markdown, markup.list.numbered.markdown meta.paragraph.list.markdown, markup.list.numbered.markdown punctuation.definition.list_item.markdown + + , markup.list.unnumbered.markdown, + , markup.list.unnumbered.markdown meta.paragraph.list.markdown, + , markup.list.numbered.markdown, + , markup.list.numbered.markdown meta.paragraph.list.markdown, + settings foreground @@ -441,6 +446,20 @@ #f1ebb900 + + name + Markdown: Lists + scope + + , markup.list.unnumbered.markdown punctuation.definition.list_item.markdown, + , markup.list.numbered.markdown punctuation.definition.list_item.markdown, + + settings + + foreground + #280000 + + name Markup: Output @@ -811,6 +830,62 @@ #28bbc677 + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #F92672 + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #46A524 + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #700DDB + + + + name + GitGutter ignored + scope + markup.ignored.git_gutter + settings + + foreground + #565656 + + + + name + GitGutter untracked + scope + markup.untracked.git_gutter + settings + + foreground + #565656 + + uuid BF4E1964-0DB9-4E88-8142-E8F52D7EDEEC diff --git a/MarkdownEditor.tmTheme b/MarkdownEditor.tmTheme index fc6ddd3f..c98615fe 100644 --- a/MarkdownEditor.tmTheme +++ b/MarkdownEditor.tmTheme @@ -430,9 +430,14 @@ name - Markdown: Punctuation + Markdown: Lists scope - markup.list.unnumbered.markdown meta.paragraph.list.markdown, markup.list.numbered.markdown, markup.list.numbered.markdown meta.paragraph.list.markdown + + , markup.list.unnumbered.markdown, + , markup.list.unnumbered.markdown meta.paragraph.list.markdown, + , markup.list.numbered.markdown, + , markup.list.numbered.markdown meta.paragraph.list.markdown, + settings foreground @@ -441,6 +446,20 @@ #EEEEEE00 + + name + Markdown: Lists + scope + + , markup.list.unnumbered.markdown punctuation.definition.list_item.markdown, + , markup.list.numbered.markdown punctuation.definition.list_item.markdown, + + settings + + foreground + #000000 + + name Markup: Output @@ -811,6 +830,62 @@ #00bdff77 + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #F92672 + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #46A524 + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #700DDB + + + + name + GitGutter ignored + scope + markup.ignored.git_gutter + settings + + foreground + #565656 + + + + name + GitGutter untracked + scope + markup.untracked.git_gutter + settings + + foreground + #565656 + + uuid BF4E1964-0DB9-4E88-8142-E8F52D7EDEEC diff --git a/MultiMarkdown.sublime-settings b/MultiMarkdown.sublime-settings index 4151585f..4829ead3 100644 --- a/MultiMarkdown.sublime-settings +++ b/MultiMarkdown.sublime-settings @@ -44,12 +44,44 @@ // List bullets to be used for automatically switching. In their order. "mde.list_indent_bullets": ["*", "-", "+"], - // Allways keep current line vertically centered. + // Always keep current line vertically centered. "mde.keep_centered": false, - // Distraction free mode improvements. In order these to work, you have to install + // Distraction free mode improvements. In order for these to work, you have to install // FullScreenStatus plugin: https://github.com/maliayas/SublimeText_FullScreenStatus "mde.distraction_free_mode": { "mde.keep_centered": true + }, + + "lint":{ + // disabled rules, e.g. "md001". + "disable": ["md013"], + // Options: + // atx, ## title only + // atx_closed, ## title ## only + // setext, title only + // ===== + // any, consistent within the document + "md003": "any", + // Options: + // asterisk, * only + // plus, + only + // dash, - only + // cyclic, different symbols on different levels + // and same symbol on same level + // single, same symbol on different levels + // any, same symbol on same level + "md004": "cyclic", + // Number of spaces per list indent. Set to 0 to use Sublime tab_size instead + "md007": 0, + // Maximum line length, Set to 0 to use Sublime wrap_width instead + "md013": 0, + // Disallowed trailing punctuation in headers + "md026": ".,;:!", + // Options: + // one, '1.' only + // ordered, ascending number + // any, consistent within one list + "md029": "any" } } diff --git a/README.md b/README.md index 0a8ac63f..55fba264 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,6 @@ Markdown plugin for Sublime Text. Provides a decent Markdown color scheme (light [Dark][github 2] and [yellow][github 3] theme available. -> Your kind donations will help [me](https://github.com/maliayas) pause my daily job and put more serious effort into the development of this plugin for the next 2 milestones ([2.0.5](https://github.com/SublimeText-Markdown/MarkdownEditing/issues?milestone=1&state=open) and [2.2.0](https://github.com/SublimeText-Markdown/MarkdownEditing/issues?milestone=2&state=open)). When they are completed, donation button will be removed. Thanks. -> -> [paypal] - ## Overview * [Features](#features) @@ -23,6 +19,7 @@ Markdown plugin for Sublime Text. Provides a decent Markdown color scheme (light * [Known Bugs](#known-bugs) * [Contributing](#contributing) * [Credits](#credits) +* [Donation](#donation) * [License](#license) ## Features @@ -56,9 +53,10 @@ Markdown plugin for Sublime Text. Provides a decent Markdown color scheme (light | K | ShiftWinK | Inserts an inline image. | B I | CtrlShiftB CtrlShiftI | These are bound to bold and italic. They work both with and without selections. If there is no selection, they will just transform the word under the cursor. These keybindings will unbold/unitalicize selection if it is already bold/italic. | ^1...6 | Ctrl1...6 | These will add the corresponding number of hashmarks for headlines. Works on blank lines and selected text in tandem with the above headline tools. If you select an entire existing headline, the current hashmarks will be removed and replaced with the header level you requested. This command respects the `mde.match_header_hashes` preference setting. -| 6 | Ctrl6 | Inserts a footnote and jump to its definition. If your cursor is in a definition, it will jump back to the marker. +| 6 | CtrlShift6 | Inserts a footnote and jump to its definition. If your cursor is in a definition, it will jump back to the marker. | F | AltShiftF | Locates footnote markers without definitions and inserts their markers for the definition. | G | AltShiftG | Locates link references without definitions and inserts their labels at the bottom for the definition. +| M | CtrlShiftM | Performs lint on current Markdown file. See [lint rules](lint_docs/RULES.md). ## GFM Specific Features @@ -94,13 +92,17 @@ Scans document for referenced link usages (`[some link][some_ref]` and `[some li ## Installation +> __Important Note About Installation__ +> +> Are you getting this error after installation: _**Error loading syntax file** "Packages/Markdown/Markdown.tmLanguage": Unable to open Packages/Markdown/Markdown.tmLanguage_? This is caused by open markdown files at the install time. You have to __manually change their syntax to your newly installed Markdown syntax__. Read the below paragraph for more details on this. + _Note_: Sublime text has a native tiny package for Markdown. However, when MarkdownEditing is enabled, native package causes some conflicts. For this reason, MarkdownEditing will automatically disable it. Since it doesn't bring anything new over MarkdownEditing, this is not a loss. But remember, when you disable MarkdownEditing, you have to reenable the native one manually (if you want). If you are using Sublime Text 2, you have to disable the native package _manually_. To do that, add `Markdown` to your `ignored_packages` list in ST user settings: "ignored_packages": [..., "Markdown"], -### [Package Control][wbond] +### Package Control The preferred method of installation is via [Sublime Package Control][wbond]. @@ -177,6 +179,12 @@ This plugin contains portions of code from [Knockdown][]. Footnote commands were submitted by [J. Nicholas Geist][github 4] and originated at [geekabouttown][geekabouttown]. +## Donation + +You can support [contributors](https://github.com/SublimeText-Markdown/MarkdownEditing/graphs/contributors) of this project individually. Every contributor is welcomed to add his/her line below with any content. Ordering shall be alphabetically by GitHub username. + +* [@maliayas](https://github.com/maliayas): [paypal] ![donation received](http://maliayas.com/business/donation/badge.php?project=markdown_editing) + ## License MarkdownEditing is released under the [MIT License][opensource]. diff --git a/gather_missing_links.py b/gather_missing_links.py index 8d6fc53c..4998c112 100644 --- a/gather_missing_links.py +++ b/gather_missing_links.py @@ -1,3 +1,4 @@ +import re import sublime_plugin @@ -6,7 +7,7 @@ def run(self, edit): markers = [] self.view.find_all("\]\[([^\]]+)\]", 0, "$1", markers) self.view.find_all("\[([^\]]*)\]\[\]", 0, "$1", markers) - missinglinks = [link for link in set(markers) if not self.view.find_all("\n\s*\[%s\]:" % link)] + missinglinks = [link for link in set(markers) if not self.view.find_all("\n\s*\[%s\]:" % re.escape(link))] if len(missinglinks): # Remove all whitespace at the end of the file whitespace_at_end = self.view.find(r'\s*\z', 0) diff --git a/indent_list_item.py b/indent_list_item.py index fcdec9c4..d1cc5a2b 100644 --- a/indent_list_item.py +++ b/indent_list_item.py @@ -17,6 +17,10 @@ def run(self, edit, reverse = False): for key, bullet in enumerate(bullets): if bullet in new_line: + if reverse and new_line.startswith(bullet) and key is 0: + # In this case, do not switch bullets + continue + new_line = new_line.replace(bullet, bullets[(key + (1 if not reverse else -1)) % len(bullets)]) break diff --git a/indent_list_multiitem.py b/indent_list_multiitem.py new file mode 100644 index 00000000..ae46e135 --- /dev/null +++ b/indent_list_multiitem.py @@ -0,0 +1,63 @@ +import sublime_plugin +import re + + +class IndentListMultiitemCommand(sublime_plugin.TextCommand): + + def run(self, edit, reverse=False): + todo = [] + for region in self.view.sel(): + lines = self.view.line(region) + lines = self.view.split_by_newlines(lines) + for line in lines: + line_content = self.view.substr(line) + if not re.match("^\\s*(>\\s*)?[*+\\-]\\s+(.*)$", line_content): + break + bullet_pattern = "([*+\\-])" + bullet_pattern_a = "^\\s*(?:>\\s*)?(" + bullet_pattern_b = ")\\s+" + new_line = line_content + # Transform the bullet to the next/previous bullet type + if self.view.settings().get("mde.list_indent_auto_switch_bullet", True): + bullets = self.view.settings().get( + "mde.list_indent_bullets", ["*", "-", "+"]) + + for key, bullet in enumerate(bullets): + re_bullet = re.escape(bullet) + search_pattern = bullet_pattern_a + \ + re_bullet + bullet_pattern_b + if re.search(search_pattern, line_content): + if reverse and new_line.startswith(bullet) and key is 0: + # In this case, do not switch bullets + continue + + new_line = new_line.replace( + bullet, bullets[(key + (1 if not reverse else -1)) % len(bullets)]) + break + + # Determine how to indent (tab or spaces) + if self.view.settings().get("translate_tabs_to_spaces"): + tab_str = self.view.settings().get("tab_size", 4) * " " + + else: + tab_str = "\t" + + if not reverse: + # Do the indentation + new_line = re.sub( + bullet_pattern, tab_str + "\\1", new_line) + + else: + # Do the unindentation + new_line = re.sub( + tab_str + bullet_pattern, "\\1", new_line) + + # Insert the new item + todo.append([line, new_line]) + + while len(todo) > 0: + j = todo.pop() + self.view.replace(edit, j[0], j[1]) + + def is_enabled(self): + return bool(self.view.score_selector(self.view.sel()[0].a, "text.html.markdown")) diff --git a/lint.py b/lint.py new file mode 100644 index 00000000..2ebcfe45 --- /dev/null +++ b/lint.py @@ -0,0 +1,691 @@ + +import sublime +import sublime_plugin +import re + + +class mddef(object): + flag = 0 + gid = 0 + desc = "default" + finish = False + + def __init__(self, settings, view): + self.settings = settings + + def __str__(self): + return self.__class__.__name__.upper() + ' - ' + self.desc + + +class md001(mddef): + flag = re.M + desc = 'Header levels should only increment by one level at a time' + locator = r'^#{1,6}(?!#)' + + lastMatch = None + + def test(self, text, s, e): + ret = {} + if self.lastMatch: + n1 = len(self.lastMatch) + n2 = e - s + if n2 > n1: + if n2 != n1 + 1: + ret[s] = 'expected %d, %d found' % (n1 + 1, n2) + self.lastMatch = text[s: e] + return ret + + +class md002(mddef): + flag = re.M + desc = 'First header should be a h1 header' + locator = r'^(?:#{1,6}(?!#))|(?:-+|=+)' + + def test(self, text, s, e): + ret = {} + # print (text[s:e]) + self.finish = True + if re.match(r'#{1,6}(?!#)', text[s:e]): + if e - s != 1: + ret[s] = 'level %d found' % (e - s) + elif re.match('-+|=+', text[s:e]): + if not re.match('=+', text[s:e]): + ret[s] = 'level 2 found' + return ret + + +class md003(mddef): + flag = re.M + desc = 'Header style' + locator = r'^((?:-+|=+)|(?:#{1,6}(?!#).*))$' + gid = 1 + + ratx = r'^(#{1,6}(?!#)).*$' + ratxc = r'^(#{1,6}(?!#)).*?(#+)$' + rsetext = r'[\-\=]+' + + def test(self, text, s, e): + ret = {} + t = text[s:e] + if self.settings == 'atx': + if not re.match(self.ratx, t) or\ + re.match(self.ratxc, t): + ret[s] = 'expected atx' + elif self.settings == 'atx_closed': + if not re.match(self.ratxc, t): + ret[s] = 'expected atx_closed' + elif self.settings == 'setext': + if not re.match(self.rsetext, t): + ret[s] = 'expected setext' + elif self.settings == 'any': + if re.match(self.ratx, t): + if re.match(self.ratxc, t): + self.settings = 'atx_closed' + else: + self.settings = 'atx' + return self.test(text, s, e) + elif re.match(self.rsetext, t): + self.settings = 'setext' + return self.test(text, s, e) + return ret + + +class md004(mddef): + flag = re.M + desc = 'Unordered list style' + locator = r'^([ ]{0,3})[*+-](?=\s)' + eol = r'^(?=\S)' + gid = 1 + lastSym = None + lastpos = -1 + + def __init__(self, settings, view): + super(md004, self).__init__(settings, view) + self.lvs = [None, None, None] + + def test(self, text, s, e): + if self.lastpos > s: + return {} + self.lastpos = e + + ret = {} + lvstack = [] + basenspaces = e - s + sym = text[e:e + 1] + (ans, exp) = self.testsingle(sym) + if ans is None: + (ans, exp) = self.testcyc(sym, -1) + if ans is False: + ret[e] = '%s expected, %s found' % (exp, sym) + elif ans is False: + ret[e] = '%s expected, %s found' % (exp, sym) + + rest = text[e + 1:] + mr = re.search(self.eol, rest, re.M) + end = mr.start(0) if mr else len(rest) + block = rest[:end] + # print('====') + # print(block) + # print('====') + mrs = re.finditer(r'^(\s*)([*\-+])\s+', block, re.M) + for mr in mrs: + # print('====') + # print(mr.group(2)) + # print('====') + self.lastpos = e + 1 + mr.end(0) + sym = mr.group(2) + (ans, exp) = self.testsingle(sym) + if ans is None: + # cyclic or any + nspaces = len(mr.group(1)) + if nspaces < basenspaces: + lv = 0 + elif nspaces == basenspaces: + lv = -1 + else: + while len(lvstack) > 0: + n = lvstack.pop() + if n < nspaces: + lvstack.append(n) + break + lv = len(lvstack) + lvstack.append(nspaces) + # print(sym) + # print(lv) + # print(self.lvs) + (ans, exp) = self.testcyc(sym, lv) + if ans is False: + ret[e + 1 + + mr.start(2)] = '%s expected, %s found' % (exp, sym) + else: + if not ans: + ret[e + 1 + + mr.start(2)] = '%s expected, %s found' % (exp, sym) + return ret + + def testsingle(self, sym): + if self.settings == 'asterisk': + return (sym == '*', '*') + if self.settings == 'plus': + return (sym == '+', '+') + if self.settings == 'dash': + return (sym == '-', '-') + if self.settings == 'single': + if self.lastSym: + return (self.lastSym == sym, self.lastSym) + else: + self.lastSym = sym + return (True, None) + return (None, None) + + def testcyc(self, sym, lv): + if self.settings == 'cyclic': + if self.lvs[lv]: + return (self.lvs[lv] == sym, self.lvs[lv]) + else: + if (sym not in self.lvs): + self.lvs[lv] = sym + return (True, None) + else: + return (False, None) + if self.settings == 'any': + if self.lvs[lv]: + return self.lvs[lv] == sym + else: + self.lvs[lv] = sym + return (True, None) + return (None, None) + + +class md005(mddef): + flag = re.M + desc = 'Inconsistent indentation for list items at the same level' + locator = r'^([ ]{0,3})[*+-](?=\s)' + eol = r'^(?=\S)' + gid = 1 + lastpos = -1 + + def __init__(self, settings, view): + super(md005, self).__init__(settings, view) + self.lvs = {} + + def spacecheck(self, lv, nspaces): + if lv in self.lvs: + if self.lvs[lv] != nspaces: + return (False, self.lvs[lv]) + else: + self.lvs[lv] = nspaces + return (True, nspaces) + + def test(self, text, s, e): + # print(self.lastpos) + if self.lastpos > s: + return {} + self.lastpos = e + + ret = {} + lvstack = [] + sym = text[e:e + 1] + nspaces = e - s + basenspaces = e - s + (ans, exp) = self.spacecheck(-1, nspaces) + if not ans: + ret[s] = '%s expected, %s found' % (exp, nspaces) + + rest = text[e + 1:] + mr = re.search(self.eol, rest, re.M) + end = mr.start(0) if mr else len(rest) + block = rest[:end] + # print('====') + # print(block) + # print('====') + mrs = re.finditer(r'^( *)([*\-+])\s+', block, re.M) + for mr in mrs: + # print('----') + # print(mr.group(2)) + # print('----') + self.lastpos = e + 1 + mr.end(0) + sym = mr.group(2) + nspaces = len(mr.group(1)) + if nspaces < basenspaces: + lv = 0 + elif nspaces == basenspaces: + lv = -1 + else: + while len(lvstack) > 0: + n = lvstack.pop() + if n < nspaces: + lvstack.append(n) + break + lv = len(lvstack) + lvstack.append(nspaces) + (ans, exp) = self.spacecheck(lv, nspaces) + if ans is False: + ret[e + 1 + + mr.start(2)] = '%s expected, %s found' % (exp, nspaces) + return ret + + +class md006(mddef): + flag = re.M + desc = 'Consider starting bulleted lists at the beginning of the line' + locator = r'^([ ]{0,3})[*+-](?=\s)' + eol = r'^(?=\S)' + gid = 1 + lastpos = -1 + + def test(self, text, s, e): + # print(self.lastpos) + if self.lastpos > s: + return {} + self.lastpos = e + + ret = {} + lvstack = [] + sym = text[e:e + 1] + nspaces = e - s + if nspaces > 0: + ret[s] = '%d found' % nspaces + + rest = text[e + 1:] + mr = re.search(self.eol, rest, re.M) + end = mr.start(0) if mr else len(rest) + block = rest[:end] + # print('====') + # print(block) + # print('====') + mrs = re.finditer(r'^(\s*)([*\-+])\s+', block, re.M) + for mr in mrs: + # print('----') + # print(mr.group(2)) + # print('----') + self.lastpos = e + 1 + mr.end(0) + return ret + + +class md007(mddef): + flag = re.M + desc = 'Unordered list indentation' + locator = r'^([ ]{0,3})[*+-](?=\s)' + eol = r'^(?=\S)' + gid = 1 + lastpos = -1 + + def __init__(self, settings, view): + if settings == 0: + self.settings = view.settings().get("tab_size", 4) + else: + self.settings = settings + + def spacecheck(self, nspaces): + return (nspaces % self.settings == 0, '%d*n' % self.settings) + + def test(self, text, s, e): + # print(self.lastpos) + if self.lastpos > s: + return {} + self.lastpos = e + + ret = {} + nspaces = e - s + (ans, exp) = self.spacecheck(nspaces) + if not ans: + ret[s] = '%s expected, %s found' % (exp, nspaces) + + rest = text[e + 1:] + mr = re.search(self.eol, rest, re.M) + end = mr.start(0) if mr else len(rest) + block = rest[:end] + # print('====') + # print(block) + # print('====') + mrs = re.finditer(r'^( *)([*\-+])\s+', block, re.M) + for mr in mrs: + # print('----') + # print(mr.group(2)) + # print('----') + self.lastpos = e + 1 + mr.end(0) + nspaces = len(mr.group(1)) + (ans, exp) = self.spacecheck(nspaces) + if ans is False: + ret[e + 1 + + mr.start(2)] = '%s expected, %s found' % (exp, nspaces) + return ret + + +class md009(mddef): + flag = re.M + desc = 'Trailing spaces' + locator = r' +$' + + def test(self, text, s, e): + return {s: '%d spaces' % (e - s)} + + +class md010(mddef): + flag = re.M + desc = 'Hard tabs' + locator = r'\t' + + def test(self, text, s, e): + return {s: 'hard tab found'} + + +class md011(mddef): + flag = re.M + desc = 'Reversed link syntax' + locator = r'\(.*?\)\[.*?\]' + + def test(self, text, s, e): + return {s: 'reversed link syntax found'} + + +class md012(mddef): + desc = 'Multiple consecutive blank lines' + locator = r'\n{3,}' + + def test(self, text, s, e): + return {s + 1: '%d blank lines' % (e - s - 1)} + + +class md013(mddef): + flag = re.M + desc = 'Line length' + locator = r'^.+$' + + def __init__(self, settings, view): + if settings == 0: + self.settings = view.settings().get("wrap_width", 80) + else: + self.settings = settings + + def test(self, text, s, e): + t = text[s:e] + if not re.match(r'^[ ]*[>\+\-\*].+$', t): + if e - s > self.settings: + return {s: '%d characters' % (e - s)} + return {} + + +class md018(mddef): + flag = re.M + desc = 'No space after hash on atx style header' + locator = r'^#{1,6}(?![#\s]).*(? 1 and ((t[0] == ' ' and t[1] == ' ') or + (t[-1] == ' ' and t[-2] == ' ')): + return {s: 'too many spaces'} + return {} + + +class md022(mddef): + flag = re.M + desc = 'Headers should be surrounded by blank lines' + locator = r'^((?:-+|=+)|(?:#{1,6}(?!#).*))$' + + def test(self, text, s, e): + if re.match(r'-+|=+', text[s:e]): + st = text.rfind('\n', 0, s - 1) + s = st + 1 + + if s > 1 and text[s - 2] != '\n': + return {s: 'blank line required before this line'} + + if e < len(text) - 2 and text[e + 1] != '\n': + return {s: 'blank line required after this line'} + return {} + + +class md023(mddef): + flag = re.M + desc = 'Headers must start at the beginning of the line' + locator = r'^( +)((?:-+|=+)|(?:#{1,6}(?!#).*))$' + gid = 1 + + def test(self, text, s, e): + return {s: '%d spaces found' % (e - s)} + + +class md024(mddef): + flag = re.M + desc = 'Multiple headers with the same content' + locator = r'^((?:-+|=+)|(?:#{1,6}(?!#).*))$' + gid = 1 + + ratx = r'(#{1,6}(?!#)) *(.*?) *$' + ratxc = r'(#{1,6}(?!#)) *(.*?) *(#+)$' + + def __init__(self, settings, view): + super(md024, self).__init__(settings, view) + self.storage = [] + + def test(self, text, s, e): + ret = {} + title = text[s:e] + if re.match(r'-+|=+', title): + st = text.rfind('\n', 0, s - 1) + title = text[st + 1: s - 1] + else: + mr = re.match(self.ratxc, title) + if mr: + title = mr.group(2) + else: + mr = re.match(self.ratx, title) + title = mr.group(2) + if title in self.storage: + ret[s] = '%s duplicated' % repr(title) + else: + self.storage.append(title) + return ret + + +class md025(mddef): + flag = re.M + desc = 'Multiple top level headers in the same document' + locator = r'^(={3,}|#(?!#).*)$' + count = 0 + + def test(self, text, s, e): + ret = {} + self.count += 1 + if self.count > 1: + ret[s] = '%d found' % self.count + return ret + + +class md026(mddef): + flag = re.M + desc = 'Trailing punctuation in header' + locator = r'^((?:-+|=+)|(?:#{1,6}(?!#).*))$' + gid = 1 + + ratx = r'(#{1,6}(?!#)) *(.*?) *$' + ratxc = r'(#{1,6}(?!#)) *(.*?) *?(#+)$' + + def test(self, text, s, e): + ret = {} + title = text[s:e] + if re.match(r'-+|=+', title): + st = text.rfind('\n', 0, s - 1) + title = text[st + 1: s - 1] + else: + mr = re.match(self.ratxc, title) + if mr: + title = mr.group(2) + else: + mr = re.match(self.ratx, title) + title = mr.group(2) + if title[-1] in self.settings: + ret[s] = '%s found' % repr(title[-1]) + return ret + + +class md027(mddef): + flag = re.M + desc = 'Multiple spaces after blockquote symbol' + locator = r'^ {0,4}> {2,}' + + def test(self, text, s, e): + return {s: 'too many spaces'} + + +class md028(mddef): + flag = re.M + desc = 'Blank line inside blockquote' + locator = r'^ {0,4}>.*$' + lastQuoteEnd = None + + def test(self, text, s, e): + ret = {} + if self.lastQuoteEnd: + if re.match(r'(\n *){2,}', text[self.lastQuoteEnd:s]): + ret[self.lastQuoteEnd] = 'found one' + self.lastQuoteEnd = e + return ret + + +class md029(mddef): + flag = re.M + desc = 'Ordered list item prefix' + locator = r'^ {0,3}([0-9]+)\.(?=\s)' + gid = 1 + eol = r'^\s*$' + lastpos = -1 + + def test(self, text, s, e): + if self.lastpos > s: + return {} + self.lastpos = e + + sym = text[s:e] + if self.settings == 'any': + if sym == '1': + style = None + else: + style = 'ordered' + elif self.settings == 'one': + style = 'one' + elif self.settings == 'ordered': + style = 'ordered' + + rest = text[e + 1:] + mr = re.search(self.eol, rest, re.M) + end = mr.start(0) if mr else len(rest) + block = rest[:end] + mrs = re.finditer(r'^ {0,3}([0-9]+)\.(?=\s)', block, re.M) + lastSym = sym + ret = {} + for mr in mrs: + self.lastpos = e + 1 + mr.end(0) + sym = mr.group(1) + if style is None: + if sym == '1': + style = 'one' + else: + style = 'ordered' + + if style == 'one': + if sym != '1': + ret[mr.start( + 1) + e + 1] = '%s found, \'1\' expected' % repr(sym) + else: + if int(sym) != int(lastSym) + 1: + ret[mr.start(1) + e + 1] = ('%s found, \'%d\' expected' % + (repr(sym), int(lastSym) + 1)) + lastSym = sym + return ret + + +class LintCommand(sublime_plugin.TextCommand): + + blockdef = [] + scope_block = 'markup.raw.block.markdown' + + def run(self, edit): + mddef = globals()['mddef'] + text = self.view.substr(sublime.Region(0, self.view.size())) + st = self.view.settings().get('lint', {}) + uselist = [] + disablelist = st['disable'] + for cl in mddef.__subclasses__(): + if cl.__name__ not in disablelist: + uselist.append(cl) + result = [] + for mddef in uselist: + r = self.test(mddef(st[mddef.__name__] if mddef.__name__ in st + else None, self.view), text) + result.extend(r) + sublime.status_message('MarkdownLint: %d error(s) found' % len(result)) + if len(result) > 0: + result = sorted(result, key=lambda t: t[0]) + outputtxt = '' + for t in result: + (row, col) = self.view.rowcol(t[0]) + outputtxt += 'line %d: %s, %s\n' % (row + 1, t[1], t[2]) + window = sublime.active_window() + output = window.create_output_panel("mde") + output.run_command('erase_view') + output.run_command('append', {'characters': outputtxt}) + window.run_command("show_panel", {"panel": "output.mde"}) + + def test(self, tar, text): + loc = tar.locator + # print(tar) + # print(repr(loc)) + it = re.finditer(loc, text, tar.flag) + ret = [] + for mr in it: + # print('find %d,%d' % (mr.start(tar.gid), mr.end(tar.gid))) + if self.scope_block in self.view.scope_name(mr.start(0)): + if tar.__class__ not in self.blockdef: + continue + ans = tar.test(text, mr.start(tar.gid), mr.end(tar.gid)) + for p in ans: + ret.append((p, str(tar), ans[p])) + # (row, col) = self.view.rowcol(p) + # print('line %d: %s, %s' % (row + 1, tar, ans[p])) + + # if not ans: + # ret = False + # (row, col) = self.view.rowcol(mr.start(tar.gid)) + # print('line %d: %s ' % (row + 1, tar)) + if tar.finish: + break + + return ret diff --git a/lint_docs/README.md b/lint_docs/README.md new file mode 100644 index 00000000..4bc888b4 --- /dev/null +++ b/lint_docs/README.md @@ -0,0 +1,67 @@ +# Lint feature for MarkdownEditing + +This feature is still experimental. Use at your own risk. + +Open a markdown document and press `ctrl()+shift()+M` or input `MarkdownEditing: Markdown Lint` in command pallette to try it. + +## Editing rules + +All rules are described in `lint_docs/RULES.md`, and implemented in `lint.py`. When you edit a rule, remember to edit the description in `RULES.md`. + +### How rules work + +All rules are implemented as seperated subclasses of `mddef` class defined in `lint.py`. The lifespan of a rule instance is one lint process. There are several important fields in every rule class: + +| Name | Type | Comment | +|------|------|---------| +| flag | int | the flag used to search for locator (default: 0) | +| desc | str | description of the rule | +| locator | str | a regex that will be used to locate the targets | +| gid | int | the id of the group in locator that will be passed to test method (default: 0) | +| finish | bool | the linter will stop scanning the rest of the document if it is true (default: false) | + +and a "test" method like this: + +```python +def test(self, text, s, e): + if isIllegal(text[s:e]): + return {the_offset: "additional information"} + else: + return {} +``` + +The linter will search for all occcurences of `locator` with regex flag equals to `flag` in the document. Then it passes the document itself and the begin position and the end position of target captured group to `test` method. The `test` method will return a dictionary of "offset:information" key-value pairs. That offset will decide the displayed line number of the occurence of the error. + +### Editing an existing rule + +First you need to know the name of that rule (e.g. MD001), and search for the class with the same name in `lint.py` (e.g. `md001`). You may want to change the `locator` to narrow down (or expand) the applied domain first before editing `test` method. + +### Creating new rules + +Every rule is a subclass of `mdddef`. Here is an example: + +```python +class md001(mddef): + flag = re.M # re.M is for multiline mode + desc = 'Header levels should only increment by one level at a time' + locator = r'^#{1,6}(?!#)' # This is for atx and atx_closed style headers + + lastMatch = None # We are comparing two successive headers, so we + # need to store the previous one + + def test(self, text, s, e): + ret = {} + if self.lastMatch: + n1 = len(self.lastMatch) # the length of the captured group + n2 = e - s # is the level of the header + if n2 > n1 and n2 != n1 + 1: + ret[s] = 'expected %d, %d found' % (n1 + 1, n2) + self.lastMatch = text[s: e] + return ret +``` + +You can create new settings as well. Just follow the examples of existing rules and the value of settings are stored as `self.settings`. + +## Discussion + +You can share your opinions through [issues](https://github.com/SublimeText-Markdown/MarkdownEditing/issues). \ No newline at end of file diff --git a/lint_docs/RULES.md b/lint_docs/RULES.md new file mode 100644 index 00000000..5d759526 --- /dev/null +++ b/lint_docs/RULES.md @@ -0,0 +1,473 @@ +# Rules + +This document contains a description of all rules, what they are checking for, +as well as an examples of documents that break the rule and corrected +versions of the examples. + +The rules are mostly [from markdownlint](https://github.com/mivok/markdownlint/blob/master/docs/RULES.md). + +## MD001 - Header levels should only increment by one level at a time + +This rule is triggered when you skip header levels in a markdown document, for +example: + + # Header 1 + + ### Header 3 + + We skipped out a 2nd level header in this document + +When using multiple header levels, nested headers should increase by only one +level at a time: + + # Header 1 + + ## Header 2 + + ### Header 3 + + #### Header 4 + + ## Another Header 2 + + ### Another Header 3 + +This rule only applies to atx and atx_closed styles of headers. + +## MD002 - First header should be a h1 header + +This rule is triggered when the first header in the document isn't a h1 header: + + ## This isn't a H1 header + + ### Another header + +The first header in the document should be a h1 header: + + # Start with a H1 header + + ## Then use a H2 for subsections + +This rule applies to all three styles of headers. + +## MD003 - Header style + +This rule is triggered when different header styles (atx, setext, and 'closed' +atx) are used in the same document: + + # ATX style H1 + + ## Closed ATX style H2 ## + + Setext style H1 + =============== + +Be consistent with the style of header used in a document: + + # ATX style H1 + + ## ATX style H2 + +Note: the configured header style can be a specific style to use (atx, +atx_closed, setext), or simply require that the usage be consistent within the +document. + +## MD004 - Unordered list style + +This rule is triggered when the symbols used in the document for unordered +list items do not match the configured unordered list style: + + * Item 1 + + Item 2 + - Item 3 + +To fix this issue, use the configured style for list items throughout the +document: + + * Item 1 + * Item 2 + * Item 3 + +Note: the configured list style can be a specific symbol to use (asterisk, +plus, dash), or simply require that the usage be consistent within the +document, or require that the three different symbols to be used cyclically on different level or same symbol on same levels. + +## MD005 - Inconsistent indentation for list items at the same level + +This rule is triggered when list items are parsed as being at the same level, +but don't have the same indentation: + + * Item 1 + * Nested Item 1 + * Nested Item 2 + * A misaligned item + +Usually this rule will be triggered because of a typo. Correct the indentation +for the list to fix it: + + * Item 1 + * Nested Item 1 + * Nested Item 2 + * Nested Item 3 + +## MD006 - Consider starting bulleted lists at the beginning of the line + +This rule is triggered when top level lists don't start at the beginning of a +line: + + Some text + + * List item + * List item + +To fix, ensure that top level list items are not indented: + + + Some test + + * List item + * List item + +Rationale: Starting lists at the beginning of the line means that nested list +items can all be indented by the same amount when an editor's indent function +or the tab key is used to indent. Starting a list 1 space in means that the +indent of the first nested list is less than the indent of the second level (3 +characters if you use 4 space tabs, or 1 character if you use 2 space tabs). + +## MD007 - Unordered list indentation + +This rule is triggered when list items are not indented by the configured +number of spaces (default: current Sublime tab_size). + +Example: + + * List item + * Nested list item indented by 3 spaces + +Corrected Example: + + * List item + * Nested list item indented by 2 spaces + +Rationale (2 space indent): indending by 2 spaces allows the content of a +nested list to be in line with the start of the content of the parent list +when a single space is used after the list marker. + +Rationale (4 space indent): Same indent as code blocks, simpler for editors to +implement. See + for more +information. + +In addition, this is a compatibility issue with multi-markdown parsers, which +require a 4 space indents. See + +for a description of the problem. + +## MD009 - Trailing spaces + +This rule is triggered on any lines that end with whitespace. To fix this, +find the line that is triggered and remove any trailing spaces from the end. + +Note: this rule can be triggered inside code/quote block. + +## MD010 - Hard tabs + +This rule is triggered on any lines that contain hard tab characters instead +of using spaces for indentation. To fix this, replace any hard tab characters +with spaces instead. + +Example: + + Some text + + * hard tab character used to indent the list item + +Corrected example: + + Some text + + * Spaces used to indent the list item instead + +## MD011 - Reversed link syntax + +This rule is triggered when text that appears to be a link is encountered, but +where the syntax appears to have been reversed (the `[]` and `()` are +reversed): + + (Incorrect link syntax)[http://www.example.com/] + +To fix this, swap the `[]` and `()` around: + + [Correct link syntax](http://www.example.com/) + +## MD012 - Multiple consecutive blank lines + +Tags: whitespace, blank_lines + +This rule is triggered when there are multiple consecutive blank lines in the +document: + + Some text here + + + Some more text here + +To fix this, delete the offending lines: + + Some text here + + Some more text here + +Note: this rule will not be triggered if there are multiple consecutive blank +lines inside code blocks. + +## MD013 - Line length + +This rule is triggered when there are lines that are longer than the +configured line length (default: current Sublime wrap_width). To fix this, split the line +up into multiple lines. + +This rule is disabled by default. + +## MD018 - No space after hash on atx style header + +This rule is triggered when when spaces are missing after the hash characters +in an atx style header: + + #Header 1 + + ##Header 2 + +To fix this, separate the header text from the hash character by a single +space: + + # Header 1 + + ## Header 2 + +## MD019 - Multiple spaces after hash on atx style header + +This rule is triggered when when more than one space is used to separate the +header text from the hash characters in an atx style header: + + # Header 1 + + ## Header 2 + +To fix this, separate the header text from the hash character by a single +space: + + # Header 1 + + ## Header 2 + +## MD020 - No space inside hashes on closed atx style header + +This rule is triggered when when spaces are missing inside the hash characters +in a closed atx style header: + + #Header 1# + + ##Header 2## + +To fix this, separate the header text from the hash character by a single +space: + + # Header 1 # + + ## Header 2 ## + +Note: this rule will fire if either side of the header is missing spaces. + +## MD021 - Multiple spaces inside hashes on closed atx style header + +This rule is triggered when when more than one space is used to separate the +header text from the hash characters in a closed atx style header: + + # Header 1 # + + ## Header 2 ## + +To fix this, separate the header text from the hash character by a single +space: + + # Header 1 # + + ## Header 2 ## + +Note: this rule will fire if either side of the header contains multiple +spaces. + +## MD022 - Headers should be surrounded by blank lines + +This rule is triggered when headers (any style) are either not preceded or not +followed by a blank line: + + # Header 1 + Some text + + Some more text + ## Header 2 + +To fix this, ensure that all headers have a blank line both before and after +(except where the header is at the beginning or end of the document): + + # Header 1 + + Some text + + Some more text + + ## Header 2 + +Rationale: Aside from asthetic reasons, some parsers, including kramdown, will +not parse headers that don't have a blank line before, and will parse them as +regular text. + +## MD023 - Headers must start at the beginning of the line + +This rule is triggered when a header is indented by one or more spaces: + + Some text + + # Indented header + +To fix this, ensure that all headers start at the beginning of the line: + + Some text + + # Header + +Rationale: Headers that don't start at the beginning of the line will not be +parsed as headers, and will instead appear as regular text. + +## MD024 - Multiple headers with the same content + +This rule is triggered if there are multiple headers in the document that have +the same text: + + # Some text + + ## Some text + +To fix this, ensure that the content of each header is different: + + # Some text + + ## Some more text + +Rationale: Some markdown parses generate anchors for headers based on the +header name, and having headers with the same content can cause problems with +this. + +## MD025 - Multiple top level headers in the same document + +This rule is triggered when a top level header is in use (the first line of +the file is a h1 header), and more than one h1 header is in use in the +document: + + # Top level header + + # Another top level header + +To fix, structure your document so that there is a single h1 header that is +the title for the document, and all later headers are h2 or lower level +headers: + + # Title + + ## Header + + ## Another header + +Rationale: A top level header is a h1 on the first line of the file, and +serves as the title for the document. If this convention is in use, then there +can not be more than one title for the document, and the entire document +should be contained within this header. + +## MD026 - Trailing punctuation in header + +This rule is triggered on any header that has a punctuation character as the +last character in the line: + + # This is a header. + +To fix this, remove any trailing punctuation: + + # This is a header + +Note: The punctuation parameter can be used to specify what characters class +as punctuation at the end of the header (defaults to `".,;:!"`). For example, you can set it to +`".,;:"` to allow headers with exclamation marks in them. + +## MD027 - Multiple spaces after blockquote symbol + +This rule is triggered when blockquotes have more than one space after the +blockquote (`>`) symbol: + + > This is a block quote with bad indentation + > there should only be one. + +To fix, remove any extraneous space: + + > This is a blockquote with correct + > indentation. + +## MD028 - Blank line inside blockquote + +This rule is triggered when two blockquote blocks are separated by nothing +except for a blank line: + + > This is a blockquote + > which is immediately followed by + + > this blockquote. Unfortunately + > In some parsers, these are treated as the same blockquote. + +To fix this, ensure that any blockquotes that are right next to each other +have some text in between: + + > This is a blockquote. + + And Jimmy also said: + + > This too is a blockquote. + +Alternatively, if they are supposed to be the same quote, then add the +blockquote symbol at the beginning of the blank line: + + > This is a blockquote. + > + > This is the same blockquote. + +Rationale: Some markdown parsers will treat two blockquotes separated by one +or more blank lines as the same blockquote, while others will treat them as +separate blockquotes. + +## MD029 - Ordered list item prefix + +This rule is triggered on ordered lists that do not either start with '1.' or +do not have a prefix that increases in numerical order (depending on the +configured style, which defaults to 'any'). + +Example valid list if the style is configured as 'one': + + 1. Do this. + 1. Do that. + 1. Done. + +Example valid list if the style is configured as 'ordered': + + 1. Do this. + 2. Do that. + 3. Done. + +Example valid list if the style is configured as 'any': + + 1. Do this. + 1. Do that. + 1. Done. + + 1. Do this. + 2. Do that. + 3. Done. diff --git a/messages.json b/messages.json index f6af548b..e2ee273b 100644 --- a/messages.json +++ b/messages.json @@ -9,5 +9,6 @@ "2.0.6": "messages/2.0.6.md", "2.0.7": "messages/2.0.7.md", "2.0.8": "messages/2.0.8.md", - "2.0.9": "messages/2.0.9.md" + "2.0.9": "messages/2.0.9.md", + "2.1.0": "messages/2.1.0.md" } diff --git a/messages/2.1.0.md b/messages/2.1.0.md new file mode 100644 index 00000000..ae2f164f --- /dev/null +++ b/messages/2.1.0.md @@ -0,0 +1,24 @@ +# MarkdownEditing 2.1.0 Changelog + +Your _MarkdownEditing_ plugin is updated. Enjoy new version. For any type of feedback you can use [GitHub issues][issues]. + +## Bug Fixes + +* Fixed an issue where non-collapsed selections in numbered lists would not get deleted upon pressing `Enter`. +* Fixed an issue where the first list entry marker is colored differently. +* Some minor issues ([#206][], [#207][], [#208][]) +* "Add Missing Link Labels" command was always re-adding labels that contains regex special chars. Fixed. ([#230][]). + +## New Features + +* Indenting or unindenting multiple lines on unnumbered lists will cycle list symbols. +* Color support for [Git Gutter](https://github.com/jisaacks/GitGutter). +* Added lint feature for Markdown files. Press `Ctrl+Shift+M` (`⌘+⇧+M`) or type `MarkdownEditing: Markdown Lint` in the command pallette to try it (by @felixhao28). Check [this document][lint-rules] for the supported rules. +* Fenced code blocks now support Lua highlighting (thanks @amclain !). + +[issues]: https://github.com/SublimeText-Markdown/MarkdownEditing/issues +[#206]: https://github.com/SublimeText-Markdown/MarkdownEditing/issues/206 +[#207]: https://github.com/SublimeText-Markdown/MarkdownEditing/issues/207 +[#208]: https://github.com/SublimeText-Markdown/MarkdownEditing/issues/208 +[#230]: https://github.com/SublimeText-Markdown/MarkdownEditing/issues/230 +[lint-rules]: https://github.com/SublimeText-Markdown/MarkdownEditing/blob/master/lint_docs/RULES.md diff --git a/numbered_list.py b/numbered_list.py index c8b5efcd..16c44173 100644 --- a/numbered_list.py +++ b/numbered_list.py @@ -9,9 +9,11 @@ def run(self, edit): num = re.search('\d', text).start() dot = text.find(".") if num == 0: - view.insert(edit, sel.end(), "\n%d. " % (int(text[:dot]) + 1,)) + view.erase(edit, sel) + view.insert(edit, sel.begin(), "\n%d. " % (int(text[:dot]) + 1,)) else: - view.insert(edit, sel.end(), "\n%s%d. " % (text[:num], int(text[num:dot]) + 1)) + view.erase(edit, sel) + view.insert(edit, sel.begin(), "\n%s%d. " % (text[:num], int(text[num:dot]) + 1)) def is_enabled(self): return bool(self.view.score_selector(self.view.sel()[0].a, "text.html.markdown"))