[gnome-builder/wip/chergert/markdown] markdown-view: better cursor tracking



commit 414f7b54ec98191893c23f052157ae919708f634
Author: Sebastien Lafargue <slafargue gnome org>
Date:   Wed Oct 7 16:39:00 2015 +0200

    markdown-view: better cursor tracking
    
    - during moves, the web view is scrolled without re-parsing it
    
    - many fixes on elements rendering so that it not breaks
      because of the added position's marker
    
    now we have per line cursor tracking and in a way
    that we don't redraw the rendered view on cursor moves
    but just scroll it.

 .../html-preview/html_preview_plugin/__init__.py   |  103 +++++++++++---
 .../html_preview_plugin/markdown-view.js           |  148 +++++++++++---------
 2 files changed, 165 insertions(+), 86 deletions(-)
---
diff --git a/plugins/html-preview/html_preview_plugin/__init__.py 
b/plugins/html-preview/html_preview_plugin/__init__.py
index 4c39580..b4b1374 100644
--- a/plugins/html-preview/html_preview_plugin/__init__.py
+++ b/plugins/html-preview/html_preview_plugin/__init__.py
@@ -39,7 +39,10 @@ from gi.repository import WebKit2
 from gi.repository import Peas
 
 TAG = re.compile(u'(<.+?>)')
+LINK_REF = re.compile(u'^\[.+?\]:')
+TABLEROW_REF = re.compile(u'^\|(?![ \t]*[-:])(.+)')
 IDENT = u'53bde44bb4f156e94a85723fe633c80b54f11f69'
+RIDENT = IDENT[::-1]
 B = re.compile(u'(?<=[a-zA-Z\d\u4e00-\u9fa5])\B(?=[a-zA-Z\d\u4e00-\u9fa5])')
 
 class HtmlPreviewData(GObject.Object, Builder.ApplicationAddin):
@@ -102,6 +105,9 @@ class HtmlPreviewView(Builder.View):
     def __init__(self, document, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.document = document
+        self.line = -1
+        self.loaded = False
+        self.changed = False
 
         self.webview = WebKit2.WebView(visible=True, expand=True)
         self.add(self.webview)
@@ -113,9 +119,13 @@ class HtmlPreviewView(Builder.View):
         if language and language.get_id() == 'markdown':
             self.markdown = True
 
+        self.webview.connect ('load-changed', self.on_webview_loaded)
         document.connect('changed', self.on_changed)
         self.on_changed(document)
 
+        document.connect('notify::cursor-position', self.on_cursor_position_changed)
+        self.webview.connect_object ('destroy', self.on_webview_closed, document)
+
     def do_get_title(self):
         title = self.document.get_title()
         return '%s (Preview)' % title
@@ -124,26 +134,30 @@ class HtmlPreviewView(Builder.View):
         return self.document
 
     def get_markdown(self, text):
-        # Determine the location of the insert cursor.
-        insert = self.document.get_insert()
-        iter = self.document.get_iter_at_mark(insert)
-        line = iter.get_line()
-
-        # However, we want to try to give some context while editing.
-        # So try to give 10 lines of context above.
-        #if line < 10:
-        #    line = 0
-        #else:
-        #    line -= 10
-
         lines = text.split('\n')
-        if TAG.search(lines[line]) is not None:
-            lines[line] = TAG.sub(u'\\1 ' + IDENT, lines[line], 1)
-        else:
-            lines[line] = B.sub(IDENT, lines[line], 1)
+        new_lines = []
+
+        for index, line in enumerate(lines):
+            prefix = IDENT + str(index).zfill(6)
+
+            if (line != ''):
+                if TAG.search(line) is not None:
+                    new_line = TAG.sub(u'\\1 ' + prefix, line, 1)
+                elif LINK_REF.search(line) is not None:
+                    # we don't put ident on link reference
+                    new_line = line
+                elif TABLEROW_REF.search(line) is not None:
+                    # we put an ident only on row, after the pipe, not on a header
+                    new_line = TABLEROW_REF.sub(u'|' + prefix + '\\1', line, 1)
+                else:
+                    new_line = B.sub(prefix, line, 1)
+
+                new_lines.append(new_line.replace("\\n", "\\\\n").replace("\\r", "\\\\r").replace("\\t", 
"\\\\t"))
+            else:
+                new_lines.append(' ')
 
         # Generate our custom HTML with replaced text
-        text = '\n'.join(lines).replace("\"", "\\\"").replace("\n", "\\n")
+        text = '\n'.join(new_lines).replace("\"", "\\\"").replace("\n", "\\n")
 
         params = (HtmlPreviewData.MARKDOWN_CSS,
                   text,
@@ -158,16 +172,17 @@ class HtmlPreviewView(Builder.View):
   <script>%s</script>
   <script>%s</script>
  </head>
- <body onload="preview()">
+ <body>
   <div class="markdown-body" id="preview">
   </div>
  </body>
 </html>
 """ % params
 
-    def reload(self):
+    def on_changed(self, document):
+        self.loaded = False
+        self.changed = True
         base_uri = self.document.get_file().get_file().get_uri()
-
         begin, end = self.document.get_bounds()
         text = self.document.get_text(begin, end, True)
 
@@ -176,5 +191,49 @@ class HtmlPreviewView(Builder.View):
 
         self.webview.load_html(text, base_uri)
 
-    def on_changed(self, document):
-        self.reload()
+    def on_webview_closed (self, document):
+        document.disconnect_by_func(self.on_changed)
+        document.disconnect_by_func(self.on_cursor_position_changed)
+
+    def on_webview_loaded (self, webview, event):
+        if event == WebKit2.LoadEvent.FINISHED and self.loaded == False:
+            self.loaded = True
+            self.webview.run_javascript('preview();', None, self.on_webview_loaded_cb)
+
+    def on_webview_loaded_cb (self, webview, result):
+        res = webview.run_javascript_finish(result)
+
+        self.on_cursor_position_changed (None, self.document)
+
+    def on_cursor_position_changed (self, pspec, document):
+        if self.loaded:
+            # Determine the location of the insert cursor.
+            insert = self.document.get_insert()
+            iter = self.document.get_iter_at_mark(insert)
+            line = iter.get_line()
+
+            if (line != self.line or self.changed):
+                self.changed = False
+                # give some context while editing.
+                # So try to give 10 lines of context above.
+                context = 0
+
+                script = """
+var id;
+var line = ({0} > 0) ? {0} : 0;
+
+for (; line >= 0; --line) {{
+  id = rFlagSign + ("000000" + line).slice(-6);
+  if (document.getElementById(id)) {{
+    break;
+  }}
+}}
+
+location.hash = id;
+""".format(line - context)
+
+                self.webview.run_javascript(script, None, self.cursor_position_changed_cb)
+                self.line = line
+
+    def cursor_position_changed_cb (self, webview, result):
+        res = webview.run_javascript_finish(result)
diff --git a/plugins/html-preview/html_preview_plugin/markdown-view.js 
b/plugins/html-preview/html_preview_plugin/markdown-view.js
index 4c4ee4d..6ec800a 100644
--- a/plugins/html-preview/html_preview_plugin/markdown-view.js
+++ b/plugins/html-preview/html_preview_plugin/markdown-view.js
@@ -1,95 +1,116 @@
 // most of this file is based on markdown-preview.vim
 //
+var LINE_NUMBER_SIZE = 6;
 
 var renderer = new marked.Renderer();
+var proto = marked.Renderer.prototype;
+
 var flagSign = "53bde44bb4f156e94a85723fe633c80b54f11f69";
+var flagSign_len = flagSign.length;
 var rFlagSign = flagSign.split('').reverse().join('');
-var aPoint = '<a style="position: relative;" href="#'+ rFlagSign +'" id="'+ rFlagSign +'"></a>';
+
+function get_apoint(line_num) {
+  var flag = rFlagSign + line_num;
+
+  return '<a style="position: relative;" href="#' +
+         flag +
+         '" id="' +
+         flag +
+         '"></a>';
+}
+
+function get_line_num(text) {
+  pos = text.indexOf(flagSign);
+
+  return (pos !== -1) ? text.substring (pos +flagSign_len , pos + flagSign_len + LINE_NUMBER_SIZE) : '';
+}
+
+function escape_text(text, encode) {
+  return text
+         .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
+         .replace(/</g, '&lt;')
+         .replace(/>/g, '&gt;')
+         .replace(/"/g, '&quot;')
+         .replace(/'/g, '&#39;');
+}
+
+function replace_all(text, left_bound, right_bound, escaped) {
+  var i, len, line;
+  var line_num = '';
+  var raw = '';
+  var cleaned_text;
+
+  left_bound = left_bound || '';
+  right_bound = right_bound || '';
+  escaped = (typeof escaped !== 'undefined') ? escaped : false;
+
+  text = text.split('\n');
+
+  for(i = 0, len = text.length; i < len; i++) {
+      line = text[i];
+      line_num = get_line_num (line);
+      if(line_num !== '') {
+          cleaned_text = line.replace(flagSign + line_num, '');
+          text[i] = get_apoint(line_num) +
+                    left_bound +
+                    (escaped ? escape_text(cleaned_text, true) : cleaned_text) +
+                    right_bound;
+
+          raw += cleaned_text + '\n';
+      }
+  }
+
+  return {'tagged': text.join('\n'), 'raw': raw};
+}
 
         renderer.heading = function(text, level, raw) {
-            var result = '';
-            if (text.indexOf(flagSign) !== -1) {
-                text = text.replace(flagSign, '');
-                raw = text;
-                result = aPoint;
-            }
-            return result
-                + '<h'
-                + level
-                + ' id="'
-                + this.options.headerPrefix
-                + raw.toLowerCase().replace(/[^\w]+/g, '-')
-                + '">'
-                + text
-                + '</h'
-                + level
-                + '>\n';
+            var new_text = replace_all(text);
+            return  proto.heading.call (this, new_text.tagged, level, new_text.raw);
         };
 
-        renderer.html = function(html) {
-            var i, len, line;
-            html = html.split('\n');
-            for(i = 0, len = html.length; i < len; i++) {
-                line = html[i];
-                if(line.indexOf(flagSign) !== -1) {
-                    html[i] = line.replace(flagSign, '') + aPoint;
-                }
-            }
-            return html.join('\n');
+        renderer.html = function(text) {
+            return replace_all (text).tagged;
         };
 
         renderer.listitem = function(text) {
-            text = text.replace(flagSign, aPoint);
-            return '<li>' + text + '</li>\n';
+            return proto.listitem.call (this, replace_all(text).tagged);
         };
 
         renderer.paragraph = function(text) {
-            text = text.replace(flagSign, aPoint);
-            return '<p>' + text + '</p>\n';
+            return proto.paragraph.call (this, replace_all(text).tagged);
         };
 
-        renderer.tablerow = function(content) {
-            content = content.replace(flagSign, aPoint);
-            return '<tr>\n' + content + '</tr>\n';
+        renderer.tablecell = function(content, flags) {
+            return proto.tablecell.call (this, replace_all(content).tagged, flags);
         };
 
         renderer.codespan = function(text) {
-            var result = '';
-            if(text.indexOf(flagSign) !== -1) {
-                text = text.replace(flagSign, '');
-                result = aPoint;
-            }
-            return result + '<code>' + text + '</code>\n'
+            return proto.codespan.call (this, replace_all(text).tagged);
+        };
+
+        // webkit doesn't like link element in code, we need to trick
+        renderer.code = function(code, lang, escaped) {
+            return '<pre><code>' + replace_all(code, '<code>', '</code>', true).tagged + '</code></pre>';
         };
 
         renderer.image = function(href, title, text) {
-            var result = '';
-            if(!!title && title.indexOf(flagSign) !== -1) {
-                title = title.replace(flagSign, '');
-                result = aPoint;
-            }
-            if(!!text && text.indexOf(flagSign) !== -1) {
-                text = text.replace(flagSign, '');
-                result = aPoint;
+            var line_num = get_line_num (text);
+            if (line_num) {
+              text = text.replace(flagSign + line_num, '');
+              return  get_apoint(line_num) + proto.image.call(renderer, href, title, text);
             }
-            return result + rImage.call(renderer, href, title, text);
+
+            return proto.image.call(renderer, href, title, text);
         };
 
         renderer.link = function(href, title, text) {
-            var result = '';
-            if(!!href && href.indexOf(flagSign) !== -1) {
-                href = href.replace(flagSign, '');
-                result = aPoint;
+            var line_num = get_line_num (text);
+            if (line_num) {
+              text = text.replace(flagSign + line_num, '');
+              return get_apoint(line_num) + proto.link.call(renderer, href, title, text);
             }
-            if(!!title && title.indexOf(flagSign) !== -1) {
-                title = title.replace(flagSign, '');
-                result = aPoint;
-            }
-            if(!!text && text.indexOf(flagSign) !== -1) {
-                text = text.replace(flagSign, '');
-                result = aPoint;
-            }
-            return result + rLink.call(renderer, href, title, text);
+
+            return proto.link.call(renderer, href, title, text);
         };
 
 marked.setOptions({
@@ -105,5 +126,4 @@ marked.setOptions({
 
 function preview(){
     document.getElementById('preview').innerHTML = marked(str);
-    location.hash = '#' + rFlagSign;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]