From 353a05e29dacb8c7f8c2d9ce31336d96dd65918a Mon Sep 17 00:00:00 2001 From: ryanss Date: Tue, 10 Feb 2015 10:14:09 -0500 Subject: Wait for :HackerNews command to load python code Fixes #22 Once you import a python module into an instance of vim it does not get reloaded when a plugin manager updates the plugin. This can cause errors when, for example, a new function is added to the python module and updated vim code is trying to call it. This commit does two things: 1) Does not load the python code until the :HackerNews command is called. There is no need to load the python code during vim startup if the plugin will not be used. 2) Each time the :HackerNews command is issued and a .hackernews buffer is opened the python code is reloaded to avoid the errors previously mentioned. --- ftplugin/hackernews.py | 314 ++++++++++++++++++++++++++++++++++++++++++++++++ ftplugin/hackernews.vim | 30 ++++- plugin/hackernews.py | 314 ------------------------------------------------ plugin/hackernews.vim | 22 +--- 4 files changed, 344 insertions(+), 336 deletions(-) create mode 100644 ftplugin/hackernews.py delete mode 100644 plugin/hackernews.py diff --git a/ftplugin/hackernews.py b/ftplugin/hackernews.py new file mode 100644 index 0000000..78fcff0 --- /dev/null +++ b/ftplugin/hackernews.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- + +# vim-hackernews +# -------------- +# Browse Hacker News (news.ycombinator.com) inside Vim. +# +# Author: ryanss +# Website: https://github.com/ryanss/vim-hackernews +# License: MIT (see LICENSE file) +# Version: 0.1.1 + + +import HTMLParser +import json +import re +import textwrap +import urllib2 +import vim +import webbrowser + + +API_URL = "http://node-hnapi.herokuapp.com" +MARKDOWN_URL = "http://fuckyeahmarkdown.com/go/?read=1&u=" + + +def bwrite(s): + b = vim.current.buffer + # Never write more than two blank lines in a row + if not s.strip() and not b[-1].strip() and not b[-2].strip(): + return + + # Vim buffer.append() cannot accept unicode type, + # must first encode to UTF-8 string + if isinstance(s, unicode): + s = s.encode('utf-8', errors='replace') + + # Code block markers for syntax highlighting + if s and s[-1] == unichr(160).encode('utf-8'): + b[-1] = s + return + + if not b[0]: + b[0] = s + else: + b.append(s) + + +def main(): + vim.command("edit .hackernews") + vim.command("setlocal noswapfile") + vim.command("setlocal buftype=nofile") + + bwrite("┌───┐") + bwrite("│ Y │ Hacker New (news.ycombinator.com)") + bwrite("└───┘") + bwrite("") + + try: + news1 = json.loads(urllib2.urlopen(API_URL+"/news", timeout=5).read()) + news2 = json.loads(urllib2.urlopen(API_URL+"/news2", timeout=5).read()) + except urllib2.HTTPError, e: + print "HackerNews.vim Error: %s" % str(e) + return + except: + print "HackerNews.vim Error: HTTP Request Timeout" + return + + for i, item in enumerate(news1+news2): + if 'title' not in item: + continue + if 'domain' in item: + line = "%s%d. %s (%s) [%d]" + line %= (" " if i+1 < 10 else "", i+1, item['title'], + item['domain'], item['id']) + bwrite(line) + else: + line = "%s%d. %s [%d]" + line %= (" " if i+1 < 10 else "", i+1, item['title'], item['id']) + bwrite(line) + if item['type'] == "link": + line = "%s%d points by %s %s | %d comments [%s]" + line %= (" "*4, item['points'], item['user'], item['time_ago'], + item['comments_count'], str(item['id'])) + bwrite(line) + elif item['type'] == "job": + line = "%s%s [%d]" + line %= (" "*4, item['time_ago'], item['id']) + bwrite(line) + bwrite("") + + +def link(external=False): + line = vim.current.line + + # Search for Hacker News [item id] + m = re.search(r"\[([0-9]+)\]$", line) + if m: + id = m.group(1) + if external: + browser = webbrowser.get() + browser.open("https://news.ycombinator.com/item?id="+id) + return + try: + item = json.loads(urllib2.urlopen(API_URL+"/item/"+id, + timeout=5).read()) + except urllib2.HTTPError, e: + print "HackerNews.vim Error: %s" % str(e) + return + except: + print "HackerNews.vim Error: HTTP Request Timeout" + return + + save_pos() + del vim.current.buffer[:] + if 'domain' in item: + bwrite("%s (%s)" % (item['title'], item['domain'])) + else: + bwrite(item['title']) + if item.get('comments_count', None): + bwrite("%d points by %s %s | %d comments" + % (item['points'], item['user'], item['time_ago'], + item['comments_count'])) + else: + bwrite(item['time_ago']) + if 'url' in item: + if item['url'].find("item?id=") == 0: + item['url'] = "http://news.ycombinator.com/" + item['url'] + bwrite("[%s]" % item['url']) + if 'content' in item: + bwrite("") + print_content(item['content']) + bwrite("") + bwrite("") + print_comments(item['comments']) + return + + # Search for [http] link + b = vim.current.buffer + i = vim.current.range.start + while b[i].find("[http") < 0 and i >= 0: + # The line we were on had no part of a link in it + if b[i-1].find("]") > 0 and b[i-1].find("]") > b[i-1].find("[http"): + return + i -= 1 + start = i + if b[i].find("[http") >= 0: + if b[i].find("]") >= 0: + url = b[i][b[i].find("[http")+1:b[i].find("]")] + else: + url = b[i][b[i].find("[http")+1:] + while b[i].find("]") < 0: + if i != start: + url += b[i] + i += 1 + if i != start: + url += b[i][:b[i].find("]")] + url = url.replace(" ", "").replace("\n", "") + + if url.find("news.ycombinator.com/item?id=") > 0: + id = url[url.find("item?id=")+8:] + if external: + browser = webbrowser.get() + browser.open("https://news.ycombinator.com/item?id="+id) + return + try: + item = json.loads(urllib2.urlopen(API_URL+"/item/"+id, + timeout=5).read()) + except: + print "HackerNews.vim Error: HTTP Request Timeout" + return + save_pos() + del vim.current.buffer[:] + if 'domain' in item: + bwrite("%s (%s)" % (item['title'], item['domain'])) + else: + bwrite(item['title']) + if item.get('comments_count', None): + bwrite("%d points by %s %s | %d comments" + % (item['points'], item['user'], item['time_ago'], + item['comments_count'])) + else: + bwrite(item['time_ago']) + if 'url' in item: + bwrite("[http://news.ycombinator.com/item?id=" + str(id) + "]") + if 'content' in item: + bwrite("") + print_content(item['content']) + bwrite("") + bwrite("") + print_comments(item['comments']) + return + + if external: + browser = webbrowser.get() + browser.open(url) + return + try: + content = urllib2.urlopen(MARKDOWN_URL+url, timeout=5).read() + except urllib2.HTTPError, e: + print "HackerNews.vim Error: %s" % str(e) + return + except: + print "HackerNews.vim Error: HTTP Request Timeout" + return + save_pos() + del vim.current.buffer[:] + for i, line in enumerate(content.split('\n')): + if not line: + bwrite("") + continue + line = textwrap.wrap(line, width=80) + for j, wrap in enumerate(line): + bwrite(wrap) + return + + +def save_pos(): + marks = vim.eval("g:hackernews_marks") + m = vim.current.buffer[0].encode('hex') + marks[m] = list(vim.current.window.cursor) + vim.command("let g:hackernews_marks = %s" % str(marks)) + + +def recall_pos(): + marks = vim.eval("g:hackernews_marks") + m = vim.current.buffer[0].encode('hex') + if m in marks: + mark = marks[m] + vim.current.window.cursor = (int(mark[0]), int(mark[1])) + + +html = HTMLParser.HTMLParser() + + +def print_content(content): + for p in content.split("

"): + if not p: + continue + p = html.unescape(p) + + # Convert Text tags + # to markdown equivalent: (Text)[http://url/] + s = p.find("a>") + while s > 0: + s += 2 + section = p[:s] + m = re.search(r"(.*)", + section) + # Do not bother with anchor text if it is same as href url + if m.group(1)[:20] == m.group(2)[:20]: + p = p.replace(m.group(0), "[%s]" % m.group(1)) + else: + p = p.replace(m.group(0), + "(%s)[%s]" % (m.group(2), m.group(1))) + s = p.find("a>") + + contents = textwrap.wrap(re.sub('<[^<]+?>', '', p), width=80) + for line in contents: + if line.strip(): + bwrite(line) + if contents and line.strip(): + bwrite("") + + +def print_comments(comments): + for comment in comments: + level = comment['level'] + bwrite("%sComment by %s %s:" + % ("\t"*level, comment.get('user', '???'), comment['time_ago'])) + for p in comment['content'].split("

"): + if not p: + continue + p = html.unescape(p) + + # Extract code block before textwrap to conserve whitespace + code = None + if p.find("") >= 0: + m = re.search("

([\S\s]*?)
", p) + code = m.group(1) + p = p.replace(m.group(0), "!CODE!") + + # Convert Text tags + # to markdown equivalent: (Text)[http://url/] + s = p.find("a>") + while s > 0: + s += 2 + section = p[:s] + m = re.search(r"(.*)", + section) + # Do not bother with anchor text if it is same as href url + if m.group(1)[:20] == m.group(2)[:20]: + p = p.replace(m.group(0), "[%s]" % m.group(1)) + else: + p = p.replace(m.group(0), + "(%s)[%s]" % (m.group(2), m.group(1))) + s = p.find("a>") + + contents = textwrap.wrap(re.sub('<[^<]+?>', '', p), + width=80, + initial_indent=" "*4*level, + subsequent_indent=" "*4*level) + for line in contents: + if line.find("!CODE!") >= 0: + bwrite(" "*4*level + unichr(160)) + for c in code.split("\n"): + bwrite(" "*4*level + c) + bwrite(" "*4*level + unichr(160)) + line = " "*4*level + line.replace("!CODE!", "").strip() + if line.strip(): + bwrite(line) + if contents and line.strip(): + bwrite("") + bwrite("") + print_comments(comment['comments']) diff --git a/ftplugin/hackernews.vim b/ftplugin/hackernews.vim index b5d9372..023ac99 100644 --- a/ftplugin/hackernews.vim +++ b/ftplugin/hackernews.vim @@ -8,12 +8,34 @@ " Version: 0.1.1 +if !has('python') + echo "HackerNews.vim Error: Requires Vim compiled with +python" + finish +endif + if !exists("g:hackernews_marks") let g:hackernews_marks = {} endif -noremap o :python hacker_news_link() -noremap O :python hacker_news_link(external=True) -noremap u u:python recall_pos() -noremap :python recall_pos() +" Import Python code +execute "python import sys" +execute "python sys.path.append(r'" . expand(":p:h") . "')" + +python << EOF +if 'hackernews' not in sys.modules: + import hackernews +else: + # Reload python module to avoid errors when updating plugin + hackernews = reload(hackernews) +EOF + + +" Load front page +execute "python hackernews.main()" + + +noremap o :python hackernews.link() +noremap O :python hackernews.link(external=True) +noremap u u:python hackernews.recall_pos() +noremap :python hackernews.recall_pos() diff --git a/plugin/hackernews.py b/plugin/hackernews.py deleted file mode 100644 index 0810b9f..0000000 --- a/plugin/hackernews.py +++ /dev/null @@ -1,314 +0,0 @@ -# -*- coding: utf-8 -*- - -# vim-hackernews -# -------------- -# Browse Hacker News (news.ycombinator.com) inside Vim. -# -# Author: ryanss -# Website: https://github.com/ryanss/vim-hackernews -# License: MIT (see LICENSE file) -# Version: 0.1.1 - - -import HTMLParser -import json -import re -import textwrap -import urllib2 -import vim -import webbrowser - - -API_URL = "http://node-hnapi.herokuapp.com" -MARKDOWN_URL = "http://fuckyeahmarkdown.com/go/?read=1&u=" - - -def bwrite(s): - b = vim.current.buffer - # Never write more than two blank lines in a row - if not s.strip() and not b[-1].strip() and not b[-2].strip(): - return - - # Vim buffer.append() cannot accept unicode type, - # must first encode to UTF-8 string - if isinstance(s, unicode): - s = s.encode('utf-8', errors='replace') - - # Code block markers for syntax highlighting - if s and s[-1] == unichr(160).encode('utf-8'): - b[-1] = s - return - - if not b[0]: - b[0] = s - else: - b.append(s) - - -def hacker_news(): - vim.command("edit .hackernews") - vim.command("setlocal noswapfile") - vim.command("setlocal buftype=nofile") - - bwrite("┌───┐") - bwrite("│ Y │ Hacker News (news.ycombinator.com)") - bwrite("└───┘") - bwrite("") - - try: - news1 = json.loads(urllib2.urlopen(API_URL+"/news", timeout=5).read()) - news2 = json.loads(urllib2.urlopen(API_URL+"/news2", timeout=5).read()) - except urllib2.HTTPError, e: - print "HackerNews.vim Error: %s" % str(e) - return - except: - print "HackerNews.vim Error: HTTP Request Timeout" - return - - for i, item in enumerate(news1+news2): - if 'title' not in item: - continue - if 'domain' in item: - line = "%s%d. %s (%s) [%d]" - line %= (" " if i+1 < 10 else "", i+1, item['title'], - item['domain'], item['id']) - bwrite(line) - else: - line = "%s%d. %s [%d]" - line %= (" " if i+1 < 10 else "", i+1, item['title'], item['id']) - bwrite(line) - if item['type'] == "link": - line = "%s%d points by %s %s | %d comments [%s]" - line %= (" "*4, item['points'], item['user'], item['time_ago'], - item['comments_count'], str(item['id'])) - bwrite(line) - elif item['type'] == "job": - line = "%s%s [%d]" - line %= (" "*4, item['time_ago'], item['id']) - bwrite(line) - bwrite("") - - -def hacker_news_link(external=False): - line = vim.current.line - - # Search for Hacker News [item id] - m = re.search(r"\[([0-9]+)\]$", line) - if m: - id = m.group(1) - if external: - browser = webbrowser.get() - browser.open("https://news.ycombinator.com/item?id="+id) - return - try: - item = json.loads(urllib2.urlopen(API_URL+"/item/"+id, - timeout=5).read()) - except urllib2.HTTPError, e: - print "HackerNews.vim Error: %s" % str(e) - return - except: - print "HackerNews.vim Error: HTTP Request Timeout" - return - - save_pos() - vim.command("edit .hackernews") - if 'domain' in item: - bwrite("%s (%s)" % (item['title'], item['domain'])) - else: - bwrite(item['title']) - if item.get('comments_count', None): - bwrite("%d points by %s %s | %d comments" - % (item['points'], item['user'], item['time_ago'], - item['comments_count'])) - else: - bwrite(item['time_ago']) - if 'url' in item: - if item['url'].find("item?id=") == 0: - item['url'] = "http://news.ycombinator.com/" + item['url'] - bwrite("[%s]" % item['url']) - if 'content' in item: - bwrite("") - print_content(item['content']) - bwrite("") - bwrite("") - print_comments(item['comments']) - return - - # Search for [http] link - b = vim.current.buffer - i = vim.current.range.start - while b[i].find("[http") < 0 and i >= 0: - # The line we were on had no part of a link in it - if b[i-1].find("]") > 0 and b[i-1].find("]") > b[i-1].find("[http"): - return - i -= 1 - start = i - if b[i].find("[http") >= 0: - if b[i].find("]") >= 0: - url = b[i][b[i].find("[http")+1:b[i].find("]")] - else: - url = b[i][b[i].find("[http")+1:] - while b[i].find("]") < 0: - if i != start: - url += b[i] - i += 1 - if i != start: - url += b[i][:b[i].find("]")] - url = url.replace(" ", "").replace("\n", "") - - if url.find("news.ycombinator.com/item?id=") > 0: - id = url[url.find("item?id=")+8:] - if external: - browser = webbrowser.get() - browser.open("https://news.ycombinator.com/item?id="+id) - return - try: - item = json.loads(urllib2.urlopen(API_URL+"/item/"+id, - timeout=5).read()) - except: - print "HackerNews.vim Error: HTTP Request Timeout" - return - save_pos() - vim.command("edit .hackernews") - if 'domain' in item: - bwrite("%s (%s)" % (item['title'], item['domain'])) - else: - bwrite(item['title']) - if item.get('comments_count', None): - bwrite("%d points by %s %s | %d comments" - % (item['points'], item['user'], item['time_ago'], - item['comments_count'])) - else: - bwrite(item['time_ago']) - if 'url' in item: - bwrite("[http://news.ycombinator.com/item?id=" + str(id) + "]") - if 'content' in item: - bwrite("") - print_content(item['content']) - bwrite("") - bwrite("") - print_comments(item['comments']) - return - - if external: - browser = webbrowser.get() - browser.open(url) - return - try: - content = urllib2.urlopen(MARKDOWN_URL+url, timeout=5).read() - except urllib2.HTTPError, e: - print "HackerNews.vim Error: %s" % str(e) - return - except: - print "HackerNews.vim Error: HTTP Request Timeout" - return - save_pos() - vim.command("edit .hackernews") - for i, line in enumerate(content.split('\n')): - if not line: - bwrite("") - continue - line = textwrap.wrap(line, width=80) - for j, wrap in enumerate(line): - bwrite(wrap) - return - - -def save_pos(): - marks = vim.eval("g:hackernews_marks") - m = vim.current.buffer[0].encode('hex') - marks[m] = list(vim.current.window.cursor) - vim.command("let g:hackernews_marks = %s" % str(marks)) - - -def recall_pos(): - marks = vim.eval("g:hackernews_marks") - m = vim.current.buffer[0].encode('hex') - if m in marks: - mark = marks[m] - vim.current.window.cursor = (int(mark[0]), int(mark[1])) - - -html = HTMLParser.HTMLParser() - - -def print_content(content): - for p in content.split("

"): - if not p: - continue - p = html.unescape(p) - - # Convert Text tags - # to markdown equivalent: (Text)[http://url/] - s = p.find("a>") - while s > 0: - s += 2 - section = p[:s] - m = re.search(r"(.*)", - section) - # Do not bother with anchor text if it is same as href url - if m.group(1)[:20] == m.group(2)[:20]: - p = p.replace(m.group(0), "[%s]" % m.group(1)) - else: - p = p.replace(m.group(0), - "(%s)[%s]" % (m.group(2), m.group(1))) - s = p.find("a>") - - contents = textwrap.wrap(re.sub('<[^<]+?>', '', p), width=80) - for line in contents: - if line.strip(): - bwrite(line) - if contents and line.strip(): - bwrite("") - - -def print_comments(comments): - for comment in comments: - level = comment['level'] - bwrite("%sComment by %s %s:" - % ("\t"*level, comment.get('user', '???'), comment['time_ago'])) - for p in comment['content'].split("

"): - if not p: - continue - p = html.unescape(p) - - # Extract code block before textwrap to conserve whitespace - code = None - if p.find("") >= 0: - m = re.search("

([\S\s]*?)
", p) - code = m.group(1) - p = p.replace(m.group(0), "!CODE!") - - # Convert Text tags - # to markdown equivalent: (Text)[http://url/] - s = p.find("a>") - while s > 0: - s += 2 - section = p[:s] - m = re.search(r"(.*)", - section) - # Do not bother with anchor text if it is same as href url - if m.group(1)[:20] == m.group(2)[:20]: - p = p.replace(m.group(0), "[%s]" % m.group(1)) - else: - p = p.replace(m.group(0), - "(%s)[%s]" % (m.group(2), m.group(1))) - s = p.find("a>") - - contents = textwrap.wrap(re.sub('<[^<]+?>', '', p), - width=80, - initial_indent=" "*4*level, - subsequent_indent=" "*4*level) - for line in contents: - if line.find("!CODE!") >= 0: - bwrite(" "*4*level + unichr(160)) - for c in code.split("\n"): - bwrite(" "*4*level + c) - bwrite(" "*4*level + unichr(160)) - line = " "*4*level + line.replace("!CODE!", "").strip() - if line.strip(): - bwrite(line) - if contents and line.strip(): - bwrite("") - bwrite("") - print_comments(comment['comments']) diff --git a/plugin/hackernews.vim b/plugin/hackernews.vim index d3a6dd7..7c8fab0 100644 --- a/plugin/hackernews.vim +++ b/plugin/hackernews.vim @@ -8,24 +8,10 @@ " Version: 0.1.1 -if !has('python') - echo "HackerNews.vim Error: Requires Vim compiled with +python" - finish -endif - - -" Filetype plugins and syntax highlighting should be enabled +" Filetype plugins need to be enabled filetype plugin on -syntax on - - -" Import Python code -execute "python import sys" -execute "python sys.path.append(r'" . expand(":p:h") . "')" -execute "python from hackernews import hacker_news, hacker_news_link, recall_pos" - - -command! HackerNews python hacker_news() - +" Load ftplugin when opening .hackernews buffer au! BufRead,BufNewFile *.hackernews set filetype=hackernews + +command! HackerNews edit .hackernews -- cgit v1.2.3