Skip to content
Browse files

Keep in memory cache of notes to speed up list index updates

Relies on upstream changes in to fetch only notes changed
since a cursor.

This way though the initial load of a list index is slow, but any
subsequent updates of `:SimplenoteIndex` are _much_ faster.

This is paving the way for then storing this lightweight cache on disk
so startup from scratch is also faster. Baby steps. It's still not a
full offline cache of notes, but I still don't know if simplenote.vim
should ever do that. There are other solutions.

Also remove some blank lines, fix some typos/grammar, etc that I

References: #102
References: #96
References: #18
  • Loading branch information...
atomicules committed Nov 4, 2018
1 parent 3ccf66b commit fb41178396de7ec809e74d2e7da89e3fe1e6361e
Showing with 58 additions and 22 deletions.
  1. +18 −0
  2. +39 −21 autoload/
  3. +1 −1 autoload/
@@ -1,12 +1,14 @@
[![MIT license](](

# simplenote.vim

A vim plugin to interact with the [simplenote][1] API. You can create an account
[here][2] if you don't already have one.

Now you can take simple notes directly from your favourite editor.

## Installation

Install manually by copying `simplenote.vim` into your plugin folder or use the
included `` to generate a vimball to install.

@@ -56,6 +58,7 @@ notes in some other format (like markdown or restructured text) you can set
`g:SimplenoteFiletype` to the preferred vim filetype.

## Usage

The plugin provides several commands to interact with your Simplenote account.
In order to retrieve a list of your notes execute one of the following:

@@ -70,6 +73,12 @@ lines tall. Alternatively when `let g:SimplenoteVertical=1` is set, it is
opened as a vertical rather than horizontal split window and
`g:SimplenoteListSize=X` sets the width of the list index. You can then
navigate through the with the arrow keys and enter a note on hitting `Return`.

If you want to refresh the list index just run `:SimplenoteList` again. With
version 2.1 an in-memory cache is used to speed up subsequent updates of the
list index (the first load can still be slow if you have a lot of notes). Also
see [Single Window Mode](#single-window-mode).

Now that you see the content of the note, you can interact with this specific

@@ -148,6 +157,7 @@ Now you can jump to your todo note directly with `:Todo` in vim.

## Note sorting

simplenote.vim supports simple note ordering. Per default the sort order is
pinned notes first followed by modified date from newest to oldest. The order
can be changed by setting the `g:SimplenoteSortOrder` variable. It should be set
@@ -157,6 +167,7 @@ untagged ones), `title`, `modifydate` and `createdate` (both newer before

## Formatting

The format of the note titles in the list are configurable using the
`g:SimplenoteNoteFormat` option.

@@ -183,6 +194,7 @@ The format of the date string is also configurable using the
`"%a, %d %b %Y %H:%M:%S"`.
### Colors
If the `+conceal` feature is enabled in vim then syntax highlighting is
supported for the Simplenote note list. The highlight groups supported are:
@@ -196,6 +208,7 @@ supported for the Simplenote note list. The highlight groups supported are:
SN_NoteAgeAncient note title - ancient ('%N' format)
## Single Window Mode
By default simplenote.vim will open notes in new windows. If you would prefer
simplenote.vim to emulate the behaviour of the Simplenote website and native
applications then set `g:SimplenoteSingleWindow` (to anything) in your `.vimrc`
@@ -206,12 +219,14 @@ index and the first note opened then this new window will be targetted for
## Dependencies
Version 2 of the SimpleNote API relies heavily on JSON. As JSON and VimL don't
really play nice together, basic parts of this plugin are implemented in
python. Therefore your vim has to be compiled with python 2.7+ or 3.2+ support
in order to use this plugin.
## Usage behind proxy
Since the plugin uses Python's urllib2 for making HTTP requests, you just have
to add these lines (with the correct values) to your `.vimrc`:
@@ -221,6 +236,7 @@ to add these lines (with the correct values) to your `.vimrc`:
## Special issue concerning GFW
For Chinese mainland users, since the authentication service of [simplenote][1] is hosted
on [appspot](, a VPN connection has to be configured to use
simplenote.vim. The configurations of a VPN connection is surely beyond the scope of
@@ -231,10 +247,12 @@ exploiting `proxychains` to ease the invoking of simplenote.vim
alias simplenote="proxychains -q vim -c 'Simplenote -l'"
## Development
- [Bugs and issue tracker](
## Thanks
[mattn][6], [Tim Pope][7] and [Scrooloose][8] who write awesome vim
plugins which I took as a basis to learn how to write vim plugins.
@@ -24,8 +24,13 @@ class SimplenoteVimInterface(object):

def __init__(self, username, password):
self.simplenote = simplenote.Simplenote(username, password)
# Storing keys/ids for the note list
self.note_index = []
# Lightweight "cache" of note data for note index
self.note_cache = {}
# TODO: Maybe possible to merge the following with note_cache now?
self.note_version = {}
# Map bufnums to noteids
self.bufnum_to_noteid = {}

def get_current_note(self):
@@ -247,14 +252,14 @@ def get_notes_from_keys(self, key_list):
Returns list of fetched notes
queue = Queue()
note_list = []
note_cache = {}
for key in key_list:
t = NoteFetcher(queue, note_list, self.simplenote)
t = NoteFetcher(queue, note_cache, self.simplenote)

return note_list
return note_cache

def scratch_buffer(self, sb_name = DEFAULT_SCRATCH_NAME, sb_number = -1):
""" Opens a scratch buffer from python
@@ -338,7 +343,6 @@ def pre_rename_buffer(self):
this function is executed before the actual saving """
vim.command("let s:renaming = 1") # we just need to know if user is renaming this buffer

def update_note_from_current_buffer(self):
""" updates the currently displayed note to the web service or creates new """

@@ -366,8 +370,6 @@ def update_note_from_current_buffer(self):
vim.command("au! BufFilePre <buffer>")
vim.command("setlocal buftype=")

def update_note_to_web_service(self):

note_id = self.get_current_note()
@@ -486,11 +488,6 @@ def save_buffer_to_file(self):

vim.command("set nomodified")

def set_tags_for_current_note(self):
""" set tags for the current note"""
note_id = self.get_current_note()
@@ -507,7 +504,6 @@ def set_tags_for_current_note(self):
print("Error fetching note data.")

def trash_current_note(self):
""" trash the currently displayed note """
note_id = self.get_current_note()
@@ -521,16 +517,18 @@ def trash_current_note(self):
print("Moving note to trash failed.: %s" % note)

def delete_current_note(self):
""" trash the currently displayed note """
""" delete the currently displayed note """
note_id = self.get_current_note()
note, status = self.simplenote.delete_note(note_id)
if status == 0:
print("Note deleted.")
""" when running tests don't want to manipulate or close buffer """
if int(vim.eval("exists('g:vader_file')")) == 0:
self.remove_note_from_index(note_id, vim.current.buffer.number)
# Vim doesn't actually complete remove the buffer, but it does undo mappings, etc so we should forget this buffer.
# Vim doesn't actually completely remove the buffer, but it does undo mappings, etc so we should forget this buffer.
del self.bufnum_to_noteid[vim.current.buffer.number]
# Also need to remove from our cache
del self.note_cache[note_id]
print("Deleting note failed.: %s" % note)
@@ -682,7 +680,16 @@ def list_note_index_in_scratch_buffer(self, tags=[]):
buffer = vim.current.buffer
# Need to also keep track of the list index in the bufnum dictionary
self.bufnum_to_noteid[buffer.number] = DEFAULT_SCRATCH_NAME
note_list, status = self.simplenote.get_note_list()
if self.simplenote.current:
note_keys, status = self.simplenote.get_note_list(data=False, since=self.simplenote.current)
note_cache = self.get_notes_from_keys([n['key'] for n in note_keys])
# Merge with existing
note_keys, status = self.simplenote.get_note_list(data=False)
self.note_cache = self.get_notes_from_keys([n['key'] for n in note_keys])
note_list = list(self.note_cache.values())

if (len(tags) > 0):
note_list = [n for n in note_list if (n["deleted"] != 1 and
len(set(n["tags"]).intersection(tags)) > 0)]
@@ -692,10 +699,9 @@ def list_note_index_in_scratch_buffer(self, tags=[]):
# set global notes index object to notes
if status == 0:
note_titles = []
notes = self.get_notes_from_keys([n['key'] for n in note_list])
# Iterate through sorts here, need to reverse this because we finish with the primary sort
sortorder = list(reversed(vim.eval("s:sortorder").split(",")))
sorted_notes = notes
sorted_notes = note_list
for compare_type in sortorder:
compare_type = compare_type.strip()
if compare_type == "pinned":
@@ -727,11 +733,10 @@ def list_note_index_in_scratch_buffer(self, tags=[]):

def get_note_title(note):
""" get title of note """
note_lines = note["content"].split("\n")
return str(note_lines[0] if len(note_lines) > 0 else note["key"])
return str(note["title"])
except UnicodeEncodeError:
return unicode(note_lines[0] if len(note_lines) > 0 else note["key"])
return unicode(note["title"])

class NoteFetcher(Thread):
@@ -750,8 +755,21 @@ def __init__(self, queue, note_list, simplenote):
def run(self):
key = self.queue.get()
note, status = self.simplenote.get_note(key)
# Strip down and store a lightweight version
# Storing key "twice" as makes easier to convert to list later
note_lines = note["content"].split("\n")
note_title = note_lines[0] if len(note_lines) > 0 else note["key"]
notelight = {
"key": note["key"],
"modifydate": note["modifydate"],
"createdate": note["createdate"],
"tags": note["tags"],
"systemtags": note["systemtags"],
"deleted": note["deleted"],
"title": note_title
if status != -1:
self.note_list[note["key"]] = notelight


0 comments on commit fb41178

Please sign in to comment.
You can’t perform that action at this time.