[gnome-builder/wip/chergert/markdown] markdown-view: better cursor tracking
- From: Sébastien Lafargue <slafargue src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/markdown] markdown-view: better cursor tracking
- Date: Sat, 10 Oct 2015 22:24:13 +0000 (UTC)
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, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
+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]