# -*- encoding: UTF-8 -*- import sublime import sublime_plugin import desktop import tempfile import markdown2 import os import sys import re import json import urllib2 settings = sublime.load_settings('MarkdownPreview.sublime-settings') def getTempMarkdownPreviewPath(view): ''' return a permanent full path of the temp markdown preview file ''' tmp_filename = '%s.html' % view.id() tmp_fullpath = os.path.join(tempfile.gettempdir(), tmp_filename) return tmp_fullpath class MarkdownPreviewListener(sublime_plugin.EventListener): ''' auto update the output html if markdown file has already been converted once ''' def on_post_save(self, view): if view.file_name().endswith(tuple(settings.get('markdown_filetypes', (".md", ".markdown", ".mdown")))): temp_file = getTempMarkdownPreviewPath(view) if os.path.isfile(temp_file): # reexec markdown conversion view.run_command('markdown_preview', {'target': 'disk'}) sublime.status_message('Markdown preview file updated') class MarkdownCheatsheetCommand(sublime_plugin.TextCommand): ''' open our markdown cheat sheet in ST2 ''' def run(self, edit): cheatsheet = os.path.join(sublime.packages_path(), 'Markdown Preview', 'sample.md') self.view.window().open_file(cheatsheet) sublime.status_message('Markdown cheat sheet opened') class MarkdownPreviewCommand(sublime_plugin.TextCommand): ''' preview file contents with python-markdown and your web browser ''' def getCSS(self): ''' return the correct CSS file based on parser and settings ''' config_parser = settings.get('parser') config_css = settings.get('css') styles = '' if config_css and config_css != 'default': styles += u"" % config_css else: css_filename = 'markdown.css' if config_parser and config_parser == 'github': css_filename = 'github.css' # path via package manager css_path = os.path.join(sublime.packages_path(), 'Markdown Preview', css_filename) if not os.path.isfile(css_path): # path via git repo css_path = os.path.join(sublime.packages_path(), 'sublimetext-markdown-preview', css_filename) if not os.path.isfile(css_path): sublime.error_message('markdown.css file not found!') raise Exception("markdown.css file not found!") styles += u"" % open(css_path, 'r').read().decode('utf-8') if settings.get('allow_css_overrides'): filename = self.view.file_name() filetypes = settings.get('markdown_filetypes') if filename and filetypes: for filetype in filetypes: if filename.endswith(filetype): css_filename = filename.rpartition(filetype)[0] + '.css' if (os.path.isfile(css_filename)): styles += u"" % open(css_filename, 'r').read().decode('utf-8') return styles def get_contents(self, region): ''' Get contents or selection from view and optionally strip the YAML front matter ''' contents = self.view.substr(region) # use selection if any selection = self.view.substr(self.view.sel()[0]) if selection.strip() != '': contents = selection if settings.get('strip_yaml_front_matter') and contents.startswith('---'): title = '' title_match = re.search('(?:title:)(.+)', contents, flags=re.IGNORECASE) if title_match: stripped_title = title_match.group(1).strip() title = '%s\n%s\n\n' % (stripped_title, '=' * len(stripped_title)) contents_without_front_matter = re.sub(r'(?s)^---.*---\n', '', contents) contents = '%s%s' % (title, contents_without_front_matter) return contents def postprocessor(self, html): ''' fix relative paths in images, scripts, and links for the internal parser ''' def tag_fix(match): tag, src = match.groups() filename = self.view.file_name() if filename: if not src.startswith(('file://', 'https://', 'http://', '/', '#')): abs_path = u'file://%s/%s' % (os.path.dirname(filename), src) tag = tag.replace(src, abs_path) return tag RE_SOURCES = re.compile("""(?P<(?:img|script|a)[^>]+(?:src|href)=["'](?P[^"']+)[^>]*>)""") html = RE_SOURCES.sub(tag_fix, html) return html def convert_markdown(self, markdown): ''' convert input markdown to HTML, with github or builtin parser ''' config_parser = settings.get('parser') github_oauth_token = settings.get('github_oauth_token') markdown_html = u'cannot convert markdown' if config_parser and config_parser == 'github': # use the github API sublime.status_message('converting markdown with github API...') try: github_mode = settings.get('github_mode', 'gfm') data = {"text": markdown, "mode": github_mode} json_data = json.dumps(data) url = "https://api.github.com/markdown" sublime.status_message(url) request = urllib2.Request(url, json_data, {'Content-Type': 'application/json'}) if github_oauth_token: request.add_header('Authorization', "token %s" % github_oauth_token) markdown_html = urllib2.urlopen(request).read().decode('utf-8') except urllib2.HTTPError, e: if e.code == 401: sublime.error_message('github API auth failed. Please check your OAuth token.') else: sublime.error_message('github API responded in an unfashion way :/') except urllib2.URLError: sublime.error_message('cannot use github API to convert markdown. SSL is not included in your Python installation') except: sublime.error_message('cannot use github API to convert markdown. Please check your settings.') else: sublime.status_message('converted markdown with github API successfully') else: # convert the markdown markdown_html = markdown2.markdown(markdown, extras=['footnotes', 'toc', 'fenced-code-blocks', 'cuddled-lists']) toc_html = markdown_html.toc_html if toc_html: toc_markers = ['[toc]', '[TOC]', ''] for marker in toc_markers: markdown_html = markdown_html.replace(marker, toc_html) # postprocess the html from internal parser markdown_html = self.postprocessor(markdown_html) return markdown_html def run(self, edit, target='browser'): region = sublime.Region(0, self.view.size()) encoding = self.view.encoding() if encoding == 'Undefined': encoding = 'utf-8' elif encoding == 'Western (Windows 1252)': encoding = 'windows-1252' elif encoding == 'UTF-8 with BOM': encoding = 'utf-8' contents = self.get_contents(region) markdown_html = self.convert_markdown(contents) full_html = u'' full_html += '' % encoding full_html += self.getCSS() full_html += '' full_html += markdown_html full_html += '' full_html += '' if target in ['disk', 'browser']: # check if LiveReload ST2 extension installed and add its script to the resulting HTML livereload_installed = ('LiveReload' in os.listdir(sublime.packages_path())) if livereload_installed: full_html += '' # update output html file tmp_fullpath = getTempMarkdownPreviewPath(self.view) tmp_html = open(tmp_fullpath, 'w') tmp_html.write(full_html.encode(encoding)) tmp_html.close() # now opens in browser if needed if target == 'browser': config_browser = settings.get('browser') if config_browser and config_browser != 'default': cmd = '"%s" %s' % (config_browser, tmp_fullpath) if sys.platform == 'darwin': cmd = "open -a %s" % cmd elif sys.platform == 'linux2': cmd += ' &' result = os.system(cmd) if result != 0: sublime.error_message('cannot execute "%s" Please check your Markdown Preview settings' % config_browser) else: sublime.status_message('Markdown preview launched in %s' % config_browser) else: desktop.open(tmp_fullpath) sublime.status_message('Markdown preview launched in default html viewer') elif target == 'sublime': # create a new buffer and paste the output HTML new_view = self.view.window().new_file() new_view.set_scratch(True) new_edit = new_view.begin_edit() new_view.insert(new_edit, 0, markdown_html) new_view.end_edit(new_edit) sublime.status_message('Markdown preview launched in sublime') elif target == 'clipboard': # clipboard copy the full HTML sublime.set_clipboard(full_html) sublime.status_message('Markdown export copied to clipboard')