I broke up with neovim....vim is my best friend now

This commit is contained in:
LinlyBoi
2023-04-30 08:14:07 +03:00
parent 0d185449c5
commit 4a4a6b1e81
5245 changed files with 468325 additions and 25 deletions

View File

@@ -0,0 +1,5 @@
--langdef=org
--langmap=org:.org
--regex-org=/^(\*+)[[:space:]]+(.*)([[:space:]]+:[^\t ]*:)?$/\1 \2/s,sections/
--regex-org=/\[\[([^][]+)\]\]/\1/h,hyperlinks/
--regex-org=/\[\[[^][]+\]\[([^][]+)\]\]/\1/h,hyperlinks/

View File

@@ -0,0 +1,170 @@
" org.vim -- Text outlining and task management for Vim based on Emacs' Org-Mode
" @Author : Jan Christoph Ebersbach (jceb@e-jc.de)
" @License : AGPL3 (see http://www.gnu.org/licenses/agpl.txt)
" @Created : 2010-10-03
" @Last Modified: Tue 13. Sep 2011 20:52:57 +0200 CEST
" @Revision : 0.4
" vi: ft=vim:tw=80:sw=4:ts=4:fdm=marker
if v:version > 702
if has('python3')
let s:py_version = 'python3 '
let s:py_env = 'python3 << EOF'
elseif has('python')
let s:py_version = 'python '
let s:py_env = 'python << EOF'
else
echoerr "Unable to start orgmode. Orgmode depends on Vim >= 7.3 with Python support complied in."
finish
endif
else
echoerr "Unable to start orgmode. Orgmode depends on Vim >= 7.3 with Python support complied in."
finish
endif
" Init buffer for file {{{1
if ! exists('b:did_ftplugin')
" default emacs settings
setlocal comments=fb:*,b:#,fb:-
setlocal commentstring=#\ %s
setlocal conceallevel=2 concealcursor=nc
" original emacs settings are: setlocal tabstop=6 shiftwidth=6, but because
" of checkbox indentation the following settings are used:
setlocal tabstop=6 shiftwidth=6
if exists('g:org_tag_column')
exe 'setlocal textwidth='.g:org_tag_column
else
setlocal textwidth=77
endif
" expand tab for counting level of checkbox
setlocal expandtab
" enable % for angle brackets < >
setlocal matchpairs+=<:>
" register keybindings if they don't have been registered before
if exists("g:loaded_org")
exe s:py_version . 'ORGMODE.register_keybindings()'
endif
endif
" Load orgmode just once {{{1
if &cp || exists("g:loaded_org")
finish
endif
let g:loaded_org = 1
" Default org plugins that will be loaded (in the given order) {{{2
if ! exists('g:org_plugins') && ! exists('b:org_plugins')
let g:org_plugins = ['ShowHide', '|', 'Navigator', 'EditStructure', 'EditCheckbox', '|', 'Hyperlinks', '|', 'Todo', 'TagsProperties', 'Date', 'Agenda', 'Misc', '|', 'Export']
endif
" Default org plugin settings {{{2
" What does this do?
if ! exists('g:org_syntax_highlight_leading_stars') && ! exists('b:org_syntax_highlight_leading_stars')
let g:org_syntax_highlight_leading_stars = 1
endif
" setting to conceal aggresively
if ! exists('g:org_aggressive_conceal') && ! exists('b:org_aggressive_conceal')
let g:org_aggressive_conceal = 0
endif
" Defined in separate plugins
" Adding Behavior preference:
" 1: go into insert-mode when new heading/checkbox/plainlist added
" 0: retain original mode when new heading/checkbox/plainlist added
if ! exists('g:org_prefer_insert_mode') && ! exists('b:org_prefer_insert_mode')
let g:org_prefer_insert_mode = 1
endif
" Menu and document handling {{{1
function! <SID>OrgRegisterMenu()
exe s:py_version . 'ORGMODE.register_menu()'
endfunction
function! <SID>OrgUnregisterMenu()
exe s:py_version . 'ORGMODE.unregister_menu()'
endfunction
function! <SID>OrgDeleteUnusedDocument(bufnr)
exe s:py_env
b = int(vim.eval('a:bufnr'))
if b in ORGMODE._documents:
del ORGMODE._documents[b]
EOF
endfunction
" show and hide Org menu depending on the filetype
augroup orgmode
au BufEnter * :if &filetype == "org" | call <SID>OrgRegisterMenu() | endif
au BufLeave * :if &filetype == "org" | call <SID>OrgUnregisterMenu() | endif
au BufDelete * :call <SID>OrgDeleteUnusedDocument(expand('<abuf>'))
augroup END
" Start orgmode {{{1
" Expand our path
exec s:py_env
import glob, vim, os, sys
for p in vim.eval("&runtimepath").split(','):
dname = os.path.join(p, "ftplugin")
matches = glob.glob(dname)
for match in matches:
if os.path.exists(os.path.join(match, "orgmode")):
if match not in sys.path:
sys.path.append(match)
break
from orgmode._vim import ORGMODE, insert_at_cursor, get_user_input, date_to_str
ORGMODE.start()
from Date import Date
import datetime
EOF
" 3rd Party Plugin Integration {{{1
" * Repeat {{{2
try
call repeat#set()
catch
endtry
" * Tagbar {{{2
let g:tagbar_type_org = {
\ 'ctagstype' : 'org',
\ 'kinds' : [
\ 's:sections',
\ 'h:hyperlinks',
\ ],
\ 'sort' : 0,
\ 'deffile' : expand('<sfile>:p:h') . '/org.cnf'
\ }
" * Taglist {{{2
if exists('g:Tlist_Ctags_Cmd')
" Pass parameters to taglist
let g:tlist_org_settings = 'org;s:section;h:hyperlinks'
let g:Tlist_Ctags_Cmd .= ' --options=' . expand('<sfile>:p:h') . '/org.cnf '
endif
" * Calendar.vim {{{2
fun CalendarAction(day, month, year, week, dir)
exe s:py_version . "selected_date = " . printf("datetime.date(%d, %d, %d)", a:year, a:month, a:day)
exe s:py_version . "org_timestamp = '" . g:org_timestamp_template . "' % date_to_str(selected_date)"
" get_user_input
exe s:py_version . "modifier = get_user_input(org_timestamp)"
" change date according to user input
exe s:py_version . "newdate = Date._modify_time(selected_date, modifier)"
exe s:py_version . "newdate = date_to_str(newdate)"
" close Calendar
exe "q"
" goto previous window
exe "wincmd p"
exe s:py_version . "timestamp = '" . g:org_timestamp_template . "' % newdate"
exe s:py_version . "if modifier != None: insert_at_cursor(timestamp)"
" restore calendar_action
let g:calendar_action = g:org_calendar_action_backup
endf

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,408 @@
# -*- coding: utf-8 -*-
"""
VIM ORGMODE
~~~~~~~~~~~~
TODO
"""
import imp
import re
import sys
import vim
from datetime import datetime
import orgmode.keybinding
import orgmode.menu
import orgmode.plugins
import orgmode.settings
from orgmode.exceptions import PluginError
from orgmode.vimbuffer import VimBuffer
from orgmode.liborgmode.agenda import AgendaManager
REPEAT_EXISTS = bool(int(vim.eval('exists("*repeat#set()")')))
TAGSPROPERTIES_EXISTS = False
cache_heading = None
from orgmode.py3compat.unicode_compatibility import *
from orgmode.py3compat.encode_compatibility import *
def realign_tags(f):
u"""
Update tag alignment, dependency to TagsProperties plugin!
"""
def r(*args, **kwargs):
global TAGSPROPERTIES_EXISTS
res = f(*args, **kwargs)
if not TAGSPROPERTIES_EXISTS and u'TagsProperties' in ORGMODE.plugins:
TAGSPROPERTIES_EXISTS = True
if TAGSPROPERTIES_EXISTS:
ORGMODE.plugins[u'TagsProperties'].realign_tags()
return res
return r
def repeat(f):
u"""
Integrate with the repeat plugin if available
The decorated function must return the name of the <Plug> command to
execute by the repeat plugin.
"""
def r(*args, **kwargs):
res = f(*args, **kwargs)
if REPEAT_EXISTS and isinstance(res, basestring):
vim.command(u_encode(u'silent! call repeat#set("\\<Plug>%s")' % res))
return res
return r
def apply_count(f):
u"""
Decorator which executes function v:count or v:prevount (not implemented,
yet) times. The decorated function must return a value that evaluates to
True otherwise the function is not repeated.
"""
def r(*args, **kwargs):
count = 0
try:
count = int(vim.eval(u_encode(u'v:count')))
# visual count is not implemented yet
#if not count:
# count = int(vim.eval(u'v:prevcount'.encode(u'utf-8')))
except BaseException as e:
pass
res = f(*args, **kwargs)
count -= 1
while res and count > 0:
f(*args, **kwargs)
count -= 1
return res
return r
def echo(message):
u"""
Print a regular message that will not be visible to the user when
multiple lines are printed
"""
for m in message.split(u'\n'):
vim.command(u_encode(u':echo "%s"' % m))
def echom(message):
u"""
Print a regular message that will be visible to the user, even when
multiple lines are printed
"""
# probably some escaping is needed here
for m in message.split(u'\n'):
vim.command(u_encode(u':echomsg "%s"' % m))
def echoe(message):
u"""
Print an error message. This should only be used for serious errors!
"""
# probably some escaping is needed here
for m in message.split(u'\n'):
vim.command(u_encode(u':echoerr "%s"' % m))
def insert_at_cursor(text, move=True, start_insertmode=False):
u"""Insert text at the position of the cursor.
If move==True move the cursor with the inserted text.
"""
d = ORGMODE.get_document(allow_dirty=True)
line, col = vim.current.window.cursor
_text = d._content[line - 1]
d._content[line - 1] = _text[:col + 1] + text + _text[col + 1:]
if move:
vim.current.window.cursor = (line, col + len(text))
if start_insertmode:
vim.command(u_encode(u'startinsert'))
def get_user_input(message):
u"""Print the message and take input from the user.
Return the input or None if there is no input.
"""
try:
vim.command(u_encode(u'call inputsave()'))
vim.command(u_encode(u"let user_input = input('" + message + u": ')"))
vim.command(u_encode(u'call inputrestore()'))
return u_decode(vim.eval(u_encode(u'user_input')))
except:
return None
def get_bufnumber(bufname):
"""
Return the number of the buffer for the given bufname if it exist;
else None.
"""
for b in vim.buffers:
if b.name == bufname:
return int(b.number)
def get_bufname(bufnr):
"""
Return the name of the buffer for the given bufnr if it exist; else None.
"""
for b in vim.buffers:
if b.number == bufnr:
return b.name
def indent_orgmode():
u""" Set the indent value for the current line in the variable
b:indent_level
Vim prerequisites:
:setlocal indentexpr=Method-which-calls-indent_orgmode
:returns: None
"""
line = int(vim.eval(u_encode(u'v:lnum')))
d = ORGMODE.get_document()
heading = d.current_heading(line - 1)
if heading and line != heading.start_vim:
heading.init_checkboxes()
checkbox = heading.current_checkbox()
level = heading.level + 1
if checkbox:
if line != checkbox.start_vim:
# indent body up to the beginning of the checkbox' text
# if checkbox isn't indented to the proper location, the body
# won't be indented either
level = checkbox.level + len(checkbox.type) + 1 + \
(4 if checkbox.status else 0)
vim.command(u_encode((u'let b:indent_level = %d' % level)))
def fold_text(allow_dirty=False):
u""" Set the fold text
:setlocal foldtext=Method-which-calls-foldtext
:allow_dirty: Perform a query without (re)building the DOM if True
:returns: None
"""
line = int(vim.eval(u_encode(u'v:foldstart')))
d = ORGMODE.get_document(allow_dirty=allow_dirty)
heading = None
if allow_dirty:
heading = d.find_current_heading(line - 1)
else:
heading = d.current_heading(line - 1)
if heading:
str_heading = unicode(heading)
# expand tabs
ts = int(vim.eval(u_encode(u'&ts')))
idx = str_heading.find(u'\t')
if idx != -1:
tabs, spaces = divmod(idx, ts)
str_heading = str_heading.replace(u'\t', u' ' * (ts - spaces), 1)
str_heading = str_heading.replace(u'\t', u' ' * ts)
# Workaround for vim.command seems to break the completion menu
vim.eval(u_encode(u'SetOrgFoldtext("%s...")' % (re.sub(r'\[\[([^[\]]*\]\[)?([^[\]]+)\]\]', r'\2',
str_heading).replace( u'\\', u'\\\\').replace(u'"', u'\\"'), )))
def fold_orgmode(allow_dirty=False):
u""" Set the fold expression/value for the current line in the variable
b:fold_expr
Vim prerequisites:
:setlocal foldmethod=expr
:setlocal foldexpr=Method-which-calls-fold_orgmode
:allow_dirty: Perform a query without (re)building the DOM if True
:returns: None
"""
line = int(vim.eval(u_encode(u'v:lnum')))
d = ORGMODE.get_document(allow_dirty=allow_dirty)
heading = None
if allow_dirty:
heading = d.find_current_heading(line - 1)
else:
heading = d.current_heading(line - 1)
# if cache_heading != heading:
# heading.init_checkboxes()
# checkbox = heading.current_checkbox()
# cache_heading = heading
if heading:
# if checkbox:
# vim.command((u'let b:fold_expr = ">%d"' % heading.level + checkbox.level).encode(u'utf-8'))
if 0:
pass
elif line == heading.start_vim:
vim.command(u_encode(u'let b:fold_expr = ">%d"' % heading.level))
#elif line == heading.end_vim:
# vim.command((u'let b:fold_expr = "<%d"' % heading.level).encode(u'utf-8'))
# end_of_last_child_vim is a performance junky and is actually not needed
#elif line == heading.end_of_last_child_vim:
# vim.command((u'let b:fold_expr = "<%d"' % heading.level).encode(u'utf-8'))
else:
vim.command(u_encode(u'let b:fold_expr = %d' % heading.level))
def date_to_str(date):
if isinstance(date, datetime):
date = date.strftime(u_decode(u_encode(u'%Y-%m-%d %a %H:%M')))
else:
date = date.strftime(u_decode(u_encode(u'%Y-%m-%d %a')))
return date
class OrgMode(object):
u""" Vim Buffer """
def __init__(self):
object.__init__(self)
self.debug = bool(int(orgmode.settings.get(u'org_debug', False)))
self.orgmenu = orgmode.menu.Submenu(u'&Org')
self._plugins = {}
# list of vim buffer objects
self._documents = {}
# agenda manager
self.agenda_manager = AgendaManager()
def get_document(self, bufnr=0, allow_dirty=False):
""" Retrieve instance of vim buffer document. This Document should be
used for manipulating the vim buffer.
:bufnr: Retrieve document with bufnr
:allow_dirty: Allow the retrieved document to be dirty
:returns: vim buffer instance
"""
if bufnr == 0:
bufnr = vim.current.buffer.number
if bufnr in self._documents:
if allow_dirty or self._documents[bufnr].is_insync:
return self._documents[bufnr]
self._documents[bufnr] = VimBuffer(bufnr).init_dom()
return self._documents[bufnr]
@property
def plugins(self):
return self._plugins.copy()
@orgmode.keybinding.register_keybindings
@orgmode.keybinding.register_commands
@orgmode.menu.register_menu
def register_plugin(self, plugin):
if not isinstance(plugin, basestring):
raise ValueError(u'Parameter plugin is not of type string')
if plugin == u'|':
self.orgmenu + orgmode.menu.Separator()
self.orgmenu.children[-1].create()
return
if plugin in self._plugins:
raise PluginError(u'Plugin %s has already been loaded')
# a python module
module = None
# actual plugin class
_class = None
# locate module and initialize plugin class
try:
module = imp.find_module(plugin, orgmode.plugins.__path__)
except ImportError as e:
echom(u'Plugin not found: %s' % plugin)
if self.debug:
raise e
return
if not module:
echom(u'Plugin not found: %s' % plugin)
return
try:
module = imp.load_module(plugin, *module)
if not hasattr(module, plugin):
echoe(u'Unable to find plugin: %s' % plugin)
if self.debug:
raise PluginError(u'Unable to find class %s' % plugin)
return
_class = getattr(module, plugin)
self._plugins[plugin] = _class()
self._plugins[plugin].register()
if self.debug:
echo(u'Plugin registered: %s' % plugin)
return self._plugins[plugin]
except BaseException as e:
echoe(u'Unable to activate plugin: %s' % plugin)
echoe(u"%s" % e)
if self.debug:
import traceback
echoe(traceback.format_exc())
def register_keybindings(self):
@orgmode.keybinding.register_keybindings
def dummy(plugin):
return plugin
if sys.version_info < (3, ):
for p in self.plugins.itervalues():
dummy(p)
else:
for p in self.plugins.values():
dummy(p)
def register_menu(self):
self.orgmenu.create()
def unregister_menu(self):
vim.command(u_encode(u'silent! aunmenu Org'))
def start(self):
u""" Start orgmode and load all requested plugins
"""
plugins = orgmode.settings.get(u"org_plugins")
if not plugins:
echom(u'orgmode: No plugins registered.')
if isinstance(plugins, basestring):
try:
self.register_plugin(plugins)
except BaseException as e:
import traceback
traceback.print_exc()
elif isinstance(plugins, list) or \
isinstance(plugins, tuple):
for p in plugins:
try:
self.register_plugin(p)
except BaseException as e:
echoe('Error in %s plugin:' % p)
import traceback
traceback.print_exc()
return plugins
ORGMODE = OrgMode()

View File

@@ -0,0 +1,230 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/orgmode.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/orgmode.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/orgmode"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/orgmode"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."

View File

@@ -0,0 +1,387 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# orgmode documentation build configuration file, created by
# sphinx-quickstart on Sat May 21 15:51:55 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
import mock
# Mock vim
MOCK_MODULES = ['vim']
for m in MOCK_MODULES:
sys.modules[m] = mock.Mock()
import vim
vim.eval = mock.MagicMock(return_value=1)
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
'sphinx.ext.doctest',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.napoleon',
]
# Napoleon config
napoleon_google_docstring = True
napoleon_numpy_docstring = True
napoleon_include_private_with_doc = True
napoleon_include_special_with_doc = True
napoleon_use_admonition_for_examples = False
napoleon_use_admonition_for_notes = False
napoleon_use_admonition_for_references = False
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
# Add any paths that contain templates here, relative to this directory.
#templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'orgmode'
copyright = '2016, Author'
author = 'Author'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = ''
# The full version, including alpha/beta/rc tags.
release = ''
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
html_title = 'orgmode v0.5'
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#html_last_updated_fmt = None
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'orgmodedoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'orgmode.tex', 'orgmode Documentation',
'Author', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'orgmode', 'orgmode Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'orgmode', 'orgmode Documentation',
author, 'orgmode', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# The basename for the epub file. It defaults to the project name.
#epub_basename = project
# The HTML theme for the epub output. Since the default themes are not
# optimized for small screen space, using the same theme for HTML and epub
# output is usually not wise. This defaults to 'epub', a theme designed to save
# visual space.
#epub_theme = 'epub'
# The language of the text. It defaults to the language option
# or 'en' if the language is not set.
#epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#epub_cover = ()
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
#epub_guide = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# HTML files that should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True
# Choose between 'default' and 'includehidden'.
#epub_tocscope = 'default'
# Fix unsupported image types using the Pillow.
#epub_fix_images = False
# Scale large images.
#epub_max_image_width = 0
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#epub_show_urls = 'inline'
# If false, no index is generated.
#epub_use_index = True

View File

@@ -0,0 +1,22 @@
.. orgmode documentation master file, created by
sphinx-quickstart on Sat May 21 16:35:00 2016.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to orgmode's documentation!
===================================
Contents:
.. toctree::
:maxdepth: 4
orgmode
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -0,0 +1,281 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. epub3 to make an epub3
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
echo. coverage to run coverage check of the documentation if enabled
echo. dummy to check syntax errors of document sources
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 1>NUL 2>NUL
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\orgmode.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\orgmode.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "epub3" (
%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "coverage" (
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
if errorlevel 1 exit /b 1
echo.
echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
if "%1" == "dummy" (
%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
if errorlevel 1 exit /b 1
echo.
echo.Build finished. Dummy builder generates no files.
goto end
)
:end

View File

@@ -0,0 +1,78 @@
orgmode.liborgmode package
==========================
Submodules
----------
orgmode.liborgmode.agenda module
--------------------------------
.. automodule:: orgmode.liborgmode.agenda
:members:
:undoc-members:
:show-inheritance:
orgmode.liborgmode.agendafilter module
--------------------------------------
.. automodule:: orgmode.liborgmode.agendafilter
:members:
:undoc-members:
:show-inheritance:
orgmode.liborgmode.base module
------------------------------
.. automodule:: orgmode.liborgmode.base
:members:
:undoc-members:
:show-inheritance:
orgmode.liborgmode.checkboxes module
------------------------------------
.. automodule:: orgmode.liborgmode.checkboxes
:members:
:undoc-members:
:show-inheritance:
orgmode.liborgmode.documents module
-----------------------------------
.. automodule:: orgmode.liborgmode.documents
:members:
:undoc-members:
:show-inheritance:
orgmode.liborgmode.dom_obj module
---------------------------------
.. automodule:: orgmode.liborgmode.dom_obj
:members:
:undoc-members:
:show-inheritance:
orgmode.liborgmode.headings module
----------------------------------
.. automodule:: orgmode.liborgmode.headings
:members:
:undoc-members:
:show-inheritance:
orgmode.liborgmode.orgdate module
---------------------------------
.. automodule:: orgmode.liborgmode.orgdate
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: orgmode.liborgmode
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,110 @@
orgmode.plugins package
=======================
Submodules
----------
orgmode.plugins.Agenda module
-----------------------------
.. automodule:: orgmode.plugins.Agenda
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.Date module
---------------------------
.. automodule:: orgmode.plugins.Date
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.EditCheckbox module
-----------------------------------
.. automodule:: orgmode.plugins.EditCheckbox
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.EditStructure module
------------------------------------
.. automodule:: orgmode.plugins.EditStructure
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.Export module
-----------------------------
.. automodule:: orgmode.plugins.Export
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.Hyperlinks module
---------------------------------
.. automodule:: orgmode.plugins.Hyperlinks
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.LoggingWork module
----------------------------------
.. automodule:: orgmode.plugins.LoggingWork
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.Misc module
---------------------------
.. automodule:: orgmode.plugins.Misc
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.Navigator module
--------------------------------
.. automodule:: orgmode.plugins.Navigator
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.ShowHide module
-------------------------------
.. automodule:: orgmode.plugins.ShowHide
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.TagsProperties module
-------------------------------------
.. automodule:: orgmode.plugins.TagsProperties
:members:
:undoc-members:
:show-inheritance:
orgmode.plugins.Todo module
---------------------------
.. automodule:: orgmode.plugins.Todo
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: orgmode.plugins
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,46 @@
orgmode.py3compat package
=========================
Submodules
----------
orgmode.py3compat.encode_compatibility module
---------------------------------------------
.. automodule:: orgmode.py3compat.encode_compatibility
:members:
:undoc-members:
:show-inheritance:
orgmode.py3compat.py_py3_string module
--------------------------------------
.. automodule:: orgmode.py3compat.py_py3_string
:members:
:undoc-members:
:show-inheritance:
orgmode.py3compat.unicode_compatibility module
----------------------------------------------
.. automodule:: orgmode.py3compat.unicode_compatibility
:members:
:undoc-members:
:show-inheritance:
orgmode.py3compat.xrange_compatibility module
---------------------------------------------
.. automodule:: orgmode.py3compat.xrange_compatibility
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: orgmode.py3compat
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,71 @@
orgmode package
===============
Subpackages
-----------
.. toctree::
orgmode.liborgmode
orgmode.plugins
orgmode.py3compat
Submodules
----------
orgmode._vim module
-------------------
.. automodule:: orgmode._vim
:members:
:undoc-members:
:show-inheritance:
orgmode.exceptions module
-------------------------
.. automodule:: orgmode.exceptions
:members:
:undoc-members:
:show-inheritance:
orgmode.keybinding module
-------------------------
.. automodule:: orgmode.keybinding
:members:
:undoc-members:
:show-inheritance:
orgmode.menu module
-------------------
.. automodule:: orgmode.menu
:members:
:undoc-members:
:show-inheritance:
orgmode.settings module
-----------------------
.. automodule:: orgmode.settings
:members:
:undoc-members:
:show-inheritance:
orgmode.vimbuffer module
------------------------
.. automodule:: orgmode.vimbuffer
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: orgmode
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
class PluginError(BaseException):
def __init__(self, message):
BaseException.__init__(self, message)
class BufferNotFound(BaseException):
def __init__(self, message):
BaseException.__init__(self, message)
class BufferNotInSync(BaseException):
def __init__(self, message):
BaseException.__init__(self, message)
class HeadingDomError(BaseException):
def __init__(self, message):
BaseException.__init__(self, message)

View File

@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
import vim
MODE_ALL = u'a'
MODE_NORMAL = u'n'
MODE_VISUAL = u'v'
MODE_INSERT = u'i'
MODE_OPERATOR = u'o'
OPTION_BUFFER_ONLY = u'<buffer>'
OPTION_SLIENT = u'<silent>'
from orgmode.py3compat.encode_compatibility import *
def _register(f, name):
def r(*args, **kwargs):
p = f(*args, **kwargs)
if hasattr(p, name) and isinstance(getattr(p, name), list):
for i in getattr(p, name):
i.create()
return p
return r
def register_keybindings(f):
return _register(f, u'keybindings')
def register_commands(f):
return _register(f, u'commands')
class Command(object):
u""" A vim command """
def __init__(self, name, command, arguments=u'0', complete=None, overwrite_exisiting=False):
u"""
:name: The name of command, first character must be uppercase
:command: The actual command that is executed
:arguments: See :h :command-nargs, only the arguments need to be specified
:complete: See :h :command-completion, only the completion arguments need to be specified
"""
object.__init__(self)
self._name = name
self._command = command
self._arguments = arguments
self._complete = complete
self._overwrite_exisiting = overwrite_exisiting
def __unicode__(self):
return u':%s<CR>' % self.name
def __str__(self):
return u_encode(self.__unicode__())
@property
def name(self):
return self._name
@property
def command(self):
return self._command
@property
def arguments(self):
return self._arguments
@property
def complete(self):
return self._complete
@property
def overwrite_exisiting(self):
return self._overwrite_exisiting
def create(self):
u""" Register/create the command
"""
vim.command(u_encode(':command%(overwrite)s -nargs=%(arguments)s %(complete)s %(name)s %(command)s' %
{u'overwrite': '!' if self.overwrite_exisiting else '',
u'arguments': u_encode(self.arguments),
u'complete': '-complete=%s' % u_encode(self.complete) if self.complete else '',
u'name': self.name,
u'command': self.command}
))
class Plug(object):
u""" Represents a <Plug> to an abitrary command """
def __init__(self, name, command, mode=MODE_NORMAL):
u"""
:name: the name of the <Plug> should be ScriptnameCommandname
:command: the actual command
"""
object.__init__(self)
if mode not in (MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT, MODE_OPERATOR):
raise ValueError(u'Parameter mode not in MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT, MODE_OPERATOR')
self._mode = mode
self.name = name
self.command = command
self.created = False
def __unicode__(self):
return u'<Plug>%s' % self.name
def __str__(self):
return u_encode(self.__unicode__())
def create(self):
if not self.created:
self.created = True
cmd = self._mode
if cmd == MODE_ALL:
cmd = u''
vim.command(u_encode(u':%snoremap %s %s' % (cmd, str(self), self.command)))
@property
def mode(self):
return self._mode
class Keybinding(object):
u""" Representation of a single key binding """
def __init__(self, key, action, mode=None, options=None, remap=True, buffer_only=True, silent=True):
u"""
:key: the key(s) action is bound to
:action: the action triggered by key(s)
:mode: definition in which vim modes the key binding is valid. Should be one of MODE_*
:option: list of other options like <silent>, <buffer> ...
:repmap: allow or disallow nested mapping
:buffer_only: define the key binding only for the current buffer
"""
object.__init__(self)
self._key = key
self._action = action
# grab mode from plug if not set otherwise
if isinstance(self._action, Plug) and not mode:
mode = self._action.mode
if mode not in (MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT, MODE_OPERATOR):
raise ValueError(u'Parameter mode not in MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT, MODE_OPERATOR')
self._mode = mode
self._options = options
if self._options is None:
self._options = []
self._remap = remap
self._buffer_only = buffer_only
self._silent = silent
if self._buffer_only and OPTION_BUFFER_ONLY not in self._options:
self._options.append(OPTION_BUFFER_ONLY)
if self._silent and OPTION_SLIENT not in self._options:
self._options.append(OPTION_SLIENT)
@property
def key(self):
return self._key
@property
def action(self):
return str(self._action)
@property
def mode(self):
return self._mode
@property
def options(self):
return self._options[:]
@property
def remap(self):
return self._remap
@property
def buffer_only(self):
return self._buffer_only
@property
def silent(self):
return self._silent
def create(self):
from orgmode._vim import ORGMODE, echom
cmd = self._mode
if cmd == MODE_ALL:
cmd = u''
if not self._remap:
cmd += u'nore'
try:
create_mapping = True
if isinstance(self._action, Plug):
# create plug
self._action.create()
if int(vim.eval(u_encode(u'hasmapto("%s")' % (self._action, )))):
create_mapping = False
if isinstance(self._action, Command):
# create command
self._action.create()
if create_mapping:
vim.command(u_encode(u':%smap %s %s %s' % (cmd, u' '.join(self._options), self._key, self._action)))
except BaseException as e:
if ORGMODE.debug:
echom(u'Failed to register key binding %s %s' % (self._key, self._action))

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
u"""
Agenda
~~~~~~~~~~~~~~~~~~
The agenda is one of the main concepts of orgmode. It allows to
collect TODO items from multiple org documents in an agenda view.
Features:
* filtering
* sorting
"""
from orgmode.liborgmode.agendafilter import filter_items
from orgmode.liborgmode.agendafilter import is_within_week_and_active_todo
from orgmode.liborgmode.agendafilter import contains_active_todo
from orgmode.liborgmode.agendafilter import contains_active_date
class AgendaManager(object):
u"""Simple parsing of Documents to create an agenda."""
# TODO Move filters in this file, they do the same thing
def __init__(self):
super(AgendaManager, self).__init__()
def get_todo(self, documents):
u"""
Get the todo agenda for the given documents (list of document).
"""
filtered = []
for document in iter(documents):
# filter and return headings
filtered.extend(filter_items(document.all_headings(),
[contains_active_todo]))
return sorted(filtered)
def get_next_week_and_active_todo(self, documents):
u"""
Get the agenda for next week for the given documents (list of
document).
"""
filtered = []
for document in iter(documents):
# filter and return headings
filtered.extend(filter_items(document.all_headings(),
[is_within_week_and_active_todo]))
return sorted(filtered)
def get_timestamped_items(self, documents):
u"""
Get all time-stamped items in a time-sorted way for the given
documents (list of document).
"""
filtered = []
for document in iter(documents):
# filter and return headings
filtered.extend(filter_items(document.all_headings(),
[contains_active_date]))
return sorted(filtered)

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
u"""
agendafilter
~~~~~~~~~~~~~~~~
AgendaFilter contains all the filters that can be applied to create the
agenda.
All functions except filter_items() in the module are filters. Given a
heading they return if the heading meets the criteria of the filter.
The function filter_items() can combine different filters and only returns
the filtered headings.
"""
from datetime import datetime
from datetime import timedelta
try:
from itertools import ifilter as filter
except:
pass
def filter_items(headings, filters):
u""" Filter the given headings.
Args:
headings (list): Contains headings
filters (list): Filters that will be applied. All functions in
this module (except this function) are filters.
Returns:
filter iterator: Headings which were not filtered.
Examples:
>>> filtered = filter_items(headings, [contains_active_date,
contains_active_todo])
"""
filtered = headings
for f in filters:
filtered = filter(f, filtered)
return filtered
def is_within_week(heading):
u""" Test if headings date is within a week
Returns:
bool: True if the date in the deading is within a week in the future (or
older False otherwise.
"""
if contains_active_date(heading):
next_week = datetime.today() + timedelta(days=7)
if heading.active_date < next_week:
return True
def is_within_week_and_active_todo(heading):
u"""
Returns:
bool: True if heading contains an active TODO and the date is within a
week.
"""
return is_within_week(heading) and contains_active_todo(heading)
def contains_active_todo(heading):
u"""
Returns:
bool: True if heading contains an active TODO.
"""
# TODO make this more efficient by checking some val and not calling the
# function
# TODO why is this import failing at top level? circular dependency...
from orgmode._vim import ORGMODE
active = []
for act in ORGMODE.get_document().get_todo_states():
active.extend(act[0])
return heading.todo in active
def contains_active_date(heading):
u"""
Returns:
bool: True if heading contains an active date.
"""
return not(heading.active_date is None)

View File

@@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-
"""
base
~~~~~~~~~~
Here are some really basic data structures that are used throughout
the liborgmode.
"""
try:
from collections import UserList
except:
from UserList import UserList
try:
from collections.abc import Iterable
except ImportError:
# preserve compatibility with python < 3.10
from collections import Iterable
import sys
from orgmode.py3compat.unicode_compatibility import *
def flatten_list(lst):
""" Flattens a list
Args:
lst (iterable): An iterable that will is non-flat
Returns:
list: Flat list
"""
# TODO write tests
def gen_lst(item):
if isinstance(item, basestring) or isinstance(item, bytes):
yield item
elif isinstance(item, Iterable):
# yield from would be so nice... but c'est la vie
for val in item:
for final in gen_lst(val):
yield final
else:
yield item
return [i for i in gen_lst(lst)]
class Direction():
u"""
Direction is used to indicate the direction of certain actions.
Example: it defines the direction headings get parted in.
"""
FORWARD = 1
BACKWARD = 2
class MultiPurposeList(UserList):
u"""
A Multi Purpose List is a list that calls a user defined hook on
change. The implementation is very basic - the hook is called without any
parameters. Otherwise the Multi Purpose List can be used like any other
list.
The member element "data" can be used to fill the list without causing the
list to be marked dirty. This should only be used during initialization!
"""
def __init__(self, initlist=None, on_change=None):
UserList.__init__(self, initlist)
self._on_change = on_change
def _changed(self):
u""" Call hook """
if callable(self._on_change):
self._on_change()
def __setitem__(self, i, item):
if sys.version_info < (3, ) and isinstance(i, slice):
start, stop, _ = i.indices(len(self))
UserList.__setslice__(self, start, stop, item)
else:
UserList.__setitem__(self, i, item)
self._changed()
def __delitem__(self, i):
if sys.version_info < (3, ) and isinstance(i, slice):
start, stop, _ = i.indices(len(self))
UserList.__delslice__(self, start, stop)
else:
UserList.__delitem__(self, i)
self._changed()
def __getitem__(self, i):
if sys.version_info < (3, ):
if isinstance(i, slice):
# TODO Return just a list. Why?
return [self[i] for i in range(*i.indices(len(self)))]
# return UserList([self[i] for i in range(*i.indices(len(self)))])
return UserList.__getitem__(self, i)
# NOTE: These wrappers are necessary because of python 2
def __setslice__(self, i, j, other):
self.__setitem__(slice(i, j), other)
def __delslice__(self, i, j):
self.__delitem__(slice(i, j))
def __getslice__(self, i, j):
return self.__getitem__(slice(i, j))
def __iadd__(self, other):
res = UserList.__iadd__(self, other)
self._changed()
return res
def __imul__(self, n):
res = UserList.__imul__(self, n)
self._changed()
return res
def append(self, item):
UserList.append(self, item)
self._changed()
def insert(self, i, item):
UserList.insert(self, i, item)
self._changed()
def pop(self, i=-1):
item = self[i]
del self[i]
return item
def remove(self, item):
self.__delitem__(self.index(item))
def reverse(self):
UserList.reverse(self)
self._changed()
def sort(self, *args, **kwds):
UserList.sort(self, *args, **kwds)
self._changed()
def extend(self, other):
UserList.extend(self, other)
self._changed()
def get_domobj_range(content=[], position=0, direction=Direction.FORWARD, identify_fun=None):
u"""
Get the start and end line number of the dom obj lines from content.
:content: String to be recognized dom obj
:position: Line number in content
:direction: Search direction
:identify_fun: A identify function to recognize dom obj(Heading, Checkbox) title string.
:return: Start and end line number for the recognized dom obj.
"""
len_cb = len(content)
if position < 0 or position > len_cb:
return (None, None)
tmp_line = position
start = None
end = None
if direction == Direction.FORWARD:
while tmp_line < len_cb:
if identify_fun(content[tmp_line]) is not None:
if start is None:
start = tmp_line
elif end is None:
end = tmp_line - 1
if start is not None and end is not None:
break
tmp_line += 1
else:
while tmp_line >= 0 and tmp_line < len_cb:
if identify_fun(content[tmp_line]) is not None:
if start is None:
start = tmp_line
elif end is None:
end = tmp_line - 1
if start is not None and end is not None:
break
tmp_line -= 1 if start is None else -1
return (start, end)

View File

@@ -0,0 +1,403 @@
# -*- coding: utf-8 -*-
"""
checkboxes
~~~~~~~~~~
TODO: explain this :)
"""
import re
try:
from collections import UserList
except:
from UserList import UserList
import vim
from orgmode.liborgmode.base import MultiPurposeList, flatten_list
from orgmode.liborgmode.orgdate import OrgTimeRange
from orgmode.liborgmode.orgdate import get_orgdate
from orgmode.liborgmode.dom_obj import DomObj, DomObjList, REGEX_SUBTASK, REGEX_SUBTASK_PERCENT, REGEX_HEADING, REGEX_CHECKBOX
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
class Checkbox(DomObj):
u""" Structural checkbox object """
STATUS_ON = u'[X]'
STATUS_OFF = u'[ ]'
# intermediate status
STATUS_INT = u'[-]'
def __init__(self, level=1, type=u'-', title=u'', status=u'[ ]', body=None):
u"""
:level: Indent level of the checkbox
:type: Type of the checkbox list (-, +, *)
:title: Title of the checkbox
:status: Status of the checkbox ([ ], [X], [-])
:body: Body of the checkbox
"""
DomObj.__init__(self, level=level, title=title, body=body)
# heading
self._heading = None
self._children = CheckboxList(obj=self)
self._dirty_checkbox = False
# list type
self._type = u'-'
if type:
self.type = type
# status
self._status = Checkbox.STATUS_OFF
if status:
self.status = status
def __unicode__(self):
return u' ' * self.level + self.type + u' ' + \
(self.status + u' ' if self.status else u'') + self.title
def __str__(self):
return u_encode(self.__unicode__())
def __len__(self):
# 1 is for the heading's title
return 1 + len(self.body)
def copy(self, including_children=True, parent=None):
u"""
Create a copy of the current checkbox. The checkbox will be completely
detached and not even belong to a document anymore.
:including_children: If True a copy of all children is create as
well. If False the returned checkbox doesn't
have any children.
:parent: Don't use this parameter. It's set
automatically.
"""
checkbox = self.__class__(
level=self.level, title=self.title,
body=self.body[:])
if parent:
parent.children.append(checkbox)
if including_children and self.children:
for item in self.children:
item.copy(
including_children=including_children,
parent=checkbox)
checkbox._orig_start = self._orig_start
checkbox._orig_len = self._orig_len
checkbox._dirty_heading = self.is_dirty_checkbox
return checkbox
@classmethod
def parse_checkbox_from_data(cls, data, heading=None, orig_start=None):
u""" Construct a new checkbox from the provided data
:data: List of lines
:heading: The heading object this checkbox belongs to
:orig_start: The original start of the heading in case it was read
from a document. If orig_start is provided, the
resulting heading will not be marked dirty.
:returns: The newly created checkbox
"""
def parse_title(heading_line):
# checkbox is not heading
if REGEX_HEADING.match(heading_line) is not None:
return None
m = REGEX_CHECKBOX.match(heading_line)
if m:
r = m.groupdict()
return (len(r[u'level']), r[u'type'], r[u'status'], r[u'title'])
return None
if not data:
raise ValueError(u'Unable to create checkbox, no data provided.')
# create new checkbox
nc = cls()
nc.level, nc.type, nc.status, nc.title = parse_title(data[0])
nc.body = data[1:]
if orig_start is not None:
nc._dirty_heading = False
nc._dirty_body = False
nc._orig_start = orig_start
nc._orig_len = len(nc)
if heading:
nc._heading = heading
return nc
def update_subtasks(self, total=0, on=0):
if total != 0:
percent = (on * 100) / total
else:
percent = 0
count = "%d/%d" % (on, total)
self.title = REGEX_SUBTASK.sub("[%s]" % (count), self.title)
self.title = REGEX_SUBTASK_PERCENT.sub("[%d%%]" % (percent), self.title)
d = self._heading.document.write_checkbox(self, including_children=False)
@classmethod
def identify_checkbox(cls, line):
u""" Test if a certain line is a checkbox or not.
:line: the line to check
:returns: indent_level
"""
# checkbox is not heading
if REGEX_HEADING.match(line) is not None:
return None
m = REGEX_CHECKBOX.match(line)
if m:
r = m.groupdict()
return len(r[u'level'])
return None
@property
def is_dirty(self):
u""" Return True if the heading's body is marked dirty """
return self._dirty_checkbox or self._dirty_body
@property
def is_dirty_checkbox(self):
u""" Return True if the heading is marked dirty """
return self._dirty_checkbox
def get_index_in_parent_list(self):
""" Retrieve the index value of current checkbox in the parents list of
checkboxes. This works also for top level checkboxes.
:returns: Index value or None if heading doesn't have a
parent/document or is not in the list of checkboxes
"""
if self.parent:
return super(Checkbox, self).get_index_in_parent_list()
elif self.document:
l = self.get_parent_list()
if l:
return l.index(self)
def get_parent_list(self):
""" Retrieve the parents' list of headings. This works also for top
level headings.
:returns: List of headings or None if heading doesn't have a
parent/document or is not in the list of headings
"""
if self.parent:
return super(Checkbox, self).get_parent_list()
elif self.document:
if self in self.document.checkboxes:
return self.document.checkboxes
def set_dirty(self):
u""" Mark the heading and body dirty so that it will be rewritten when
saving the document """
self._dirty_checkbox = True
self._dirty_body = True
if self._document:
self._document.set_dirty_document()
def set_dirty_checkbox(self):
u""" Mark the checkbox dirty so that it will be rewritten when saving the
document """
self._dirty_checkbox = True
if self._document:
self._document.set_dirty_document()
@property
def previous_checkbox(self):
u""" Serialized access to the previous checkbox """
return super(Checkbox, self).previous_item
@property
def next_checkbox(self):
u""" Serialized access to the next checkbox """
return super(Checkbox, self).next_item
@property
def first_checkbox(self):
u""" Access to the first child heading or None if no children exist """
if self.children:
return self.children[0]
@property
def start(self):
u""" Access to the starting line of the checkbox """
return super(Checkbox, self).start
def toggle(self):
u""" Toggle status of this checkbox """
if self.status == Checkbox.STATUS_OFF or self.status is None:
self.status = Checkbox.STATUS_ON
else:
self.status = Checkbox.STATUS_OFF
self.set_dirty()
def all_siblings(self):
if not self.parent:
p = self._heading
else:
p = self.parent
if not p.children:
return
c = p.first_checkbox
while c:
yield c
c = c.next_sibling
return
def all_children(self):
if not self.children:
return
c = self.first_checkbox
while c:
yield c
for d in c.all_children():
yield d
c = c.next_sibling
return
def all_children_status(self):
u""" Return checkboxes status for current checkbox's all children
:return: (total, on)
total: total # of checkboxes
on: # of checkboxes which are on
"""
total, on = 0, 0
for c in self.all_children():
if c.status is not None:
total += 1
if c.status == Checkbox.STATUS_ON:
on += 1
return (total, on)
def all_siblings_status(self):
u""" Return checkboxes status for current checkbox's all siblings
:return: (total, on)
total: total # of checkboxes
on: # of checkboxes which are on
"""
total, on = 0, 0
for c in self.all_siblings():
if c.status is not None:
total += 1
if c.status == Checkbox.STATUS_ON:
on += 1
return (total, on)
def are_children_all(self, status):
u""" Check all children checkboxes status """
clen = len(self.children)
for i in range(clen):
if self.children[i].status != status:
return False
# recursively check children's status
if not self.children[i].are_children_all(status):
return False
return True
def is_child_one(self, status):
u""" Return true, if there is one child with given status """
clen = len(self.children)
for i in range(clen):
if self.children[i].status == status:
return True
return False
def are_siblings_all(self, status):
u""" Check all sibling checkboxes status """
for c in self.all_siblings():
if c.status != status:
return False
return True
@DomObj.level.setter
def level(self, value):
u""" Set the checkbox level and mark the checkbox and the document
dirty """
self._level = int(value)
self.set_dirty_checkbox()
@DomObj.title.setter
def title(self, value):
u""" Set the title and mark the document and the checkbox dirty """
if type(value) not in (unicode, str):
raise ValueError(u'Title must be a string.')
v = value
if type(v) == str:
v = u_decode(v)
self._title = v.strip()
self.set_dirty_checkbox()
@property
def status(self):
u""" status of current checkbox """
return self._status
@status.setter
def status(self, value):
self._status = value
self.set_dirty()
@status.deleter
def status(self):
self._status = u''
@property
def type(self):
u""" type of current checkbox list type """
return self._type
@type.setter
def type(self, value):
self._type = value
@type.deleter
def type(self):
self._type = u''
class CheckboxList(DomObjList):
u"""
Checkbox List
"""
def __init__(self, initlist=None, obj=None):
"""
:initlist: Initial data
:obj: Link to a concrete Checkbox or Document object
"""
# it's not necessary to register a on_change hook because the heading
# list will itself take care of marking headings dirty or adding
# headings to the deleted headings list
DomObjList.__init__(self, initlist, obj)
@classmethod
def is_checkbox(cls, obj):
return CheckboxList.is_domobj(obj)
def _get_heading(self):
if self.__class__.is_checkbox(self._obj):
return self._obj._document
return self._obj

View File

@@ -0,0 +1,312 @@
# -*- coding: utf-8 -*-
"""
documents
~~~~~~~~~
TODO: explain this :)
"""
try:
from collections import UserList
except:
from UserList import UserList
from orgmode.liborgmode.base import MultiPurposeList, flatten_list, Direction, get_domobj_range
from orgmode.liborgmode.headings import Heading, HeadingList
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
class Document(object):
u"""
Representation of a whole org-mode document.
A Document consists basically of headings (see Headings) and some metadata.
TODO: explain the 'dirty' mechanism
"""
def __init__(self):
u"""
Don't call this constructor directly but use one of the concrete
implementations.
TODO: what are the concrete implementatiions?
"""
object.__init__(self)
# is a list - only the Document methods should work on this list!
self._content = None
self._dirty_meta_information = False
self._dirty_document = False
self._meta_information = MultiPurposeList(
on_change=self.set_dirty_meta_information)
self._orig_meta_information_len = None
self._headings = HeadingList(obj=self)
self._deleted_headings = []
# settings needed to align tags properly
self._tabstop = 8
self._tag_column = 77
# TODO this doesn't differentiate between ACTIVE and FINISHED todo's
self.todo_states = [u'TODO', u'DONE']
def __unicode__(self):
if self.meta_information is None:
return u'\n'.join(self.all_headings())
return u'\n'.join(self.meta_information) + u'\n' + u'\n'.join([u'\n'.join([unicode(i)] + i.body) for i in self.all_headings()])
def __str__(self):
return u_encode(self.__unicode__())
def get_all_todo_states(self):
u""" Convenience function that returns all todo and done states and
sequences in one big list.
Returns:
list: [all todo/done states]
"""
# TODO This is not necessary remove
return flatten_list(self.get_todo_states())
def get_todo_states(self):
u""" Returns a list containing a tuple of two lists of allowed todo
states split by todo and done states. Multiple todo-done state
sequences can be defined.
Returns:
list: [([todo states], [done states]), ..]
"""
# TODO this should be made into property so todo states can be set like
# this too.. or there was also some todo property around... oh well..
# TODO there is the same method in vimbuffer
return self.todo_states
@property
def tabstop(self):
u""" Tabstop for this document """
return self._tabstop
@tabstop.setter
def tabstop(self, value):
self._tabstop = value
@property
def tag_column(self):
u""" The column all tags are right-aligned to """
return self._tag_column
@tag_column.setter
def tag_column(self, value):
self._tag_column = value
def init_dom(self, heading=Heading):
u""" Initialize all headings in document - build DOM. This method
should be call prior to accessing the document.
Returns:
self
"""
def init_heading(_h):
u"""
:returns the initialized heading
"""
start = _h.end + 1
prev_heading = None
while True:
new_heading = self.find_heading(start, heading=heading)
# * Heading 1 <- heading
# * Heading 1 <- sibling
# or
# * Heading 2 <- heading
# * Heading 1 <- parent's sibling
if not new_heading or \
new_heading.level <= _h.level:
break
# * Heading 1 <- heading
# * Heading 2 <- first child
# * Heading 2 <- another child
new_heading._parent = _h
if prev_heading:
prev_heading._next_sibling = new_heading
new_heading._previous_sibling = prev_heading
_h.children.data.append(new_heading)
# the start and end computation is only
# possible when the new heading was properly
# added to the document structure
init_heading(new_heading)
if new_heading.children:
# skip children
start = new_heading.end_of_last_child + 1
else:
start = new_heading.end + 1
prev_heading = new_heading
return _h
h = self.find_heading(heading=heading)
# initialize meta information
if h:
self._meta_information.data.extend(self._content[:h._orig_start])
else:
self._meta_information.data.extend(self._content[:])
self._orig_meta_information_len = len(self.meta_information)
# initialize dom tree
prev_h = None
while h:
if prev_h:
prev_h._next_sibling = h
h._previous_sibling = prev_h
self.headings.data.append(h)
init_heading(h)
prev_h = h
h = self.find_heading(h.end_of_last_child + 1, heading=heading)
return self
@property
def meta_information(self):
u""" Meta information is text that precedes all headings in an org-mode
document. It might contain additional information about the document,
e.g. author
"""
return self._meta_information
@meta_information.setter
def meta_information(self, value):
if self._orig_meta_information_len is None:
self._orig_meta_information_len = len(self.meta_information)
if type(value) in (list, tuple) or isinstance(value, UserList):
self._meta_information[:] = flatten_list(value)
elif type(value) in (str, ):
self._meta_information[:] = u_decode(value).split(u'\n')
elif type(value) in (unicode, ):
self._meta_information[:] = value.split(u'\n')
self.set_dirty_meta_information()
@meta_information.deleter
def meta_information(self):
self.meta_information = u''
@property
def headings(self):
u""" List of top level headings """
return self._headings
@headings.setter
def headings(self, value):
self._headings[:] = value
@headings.deleter
def headings(self):
del self.headings[:]
def write(self):
u""" Write the document
Returns:
bool: True if something was written, otherwise False
"""
raise NotImplementedError(u'Abstract method, please use concrete implementation!')
def set_dirty_meta_information(self):
u""" Mark the meta information dirty.
Note:
Causes meta information to be rewritten when saving the document
"""
self._dirty_meta_information = True
def set_dirty_document(self):
u""" Mark the whole document dirty.
Note:
When changing a heading this method must be executed in order to
changed computation of start and end positions from a static to a
dynamic computation
"""
self._dirty_document = True
@property
def is_dirty(self):
u""" Return information about unsaved changes for the document and all
related headings.
Returns:
bool: True if document contains unsaved changes.
"""
if self.is_dirty_meta_information:
return True
if self.is_dirty_document:
return True
if self._deleted_headings:
return True
return False
@property
def is_dirty_meta_information(self):
u""" Return True if the meta information is marked dirty """
return self._dirty_meta_information
@property
def is_dirty_document(self):
u""" Return True if the document is marked dirty """
return self._dirty_document
def all_headings(self):
u""" Iterate over all headings of the current document in serialized
order
:returns: Returns an iterator object which returns all headings of
the current file in serialized order
"""
if not self.headings:
return
h = self.headings[0]
while h:
yield h
h = h.next_heading
return
def find_heading(
self, position=0, direction=Direction.FORWARD, heading=Heading,
connect_with_document=True):
u""" Find heading in the given direction
Args:
position (int): starting line, counting from 0 (in vim you start
counting from 1, don't forget)
direction: downwards == Direction.FORWARD,
upwards == Direction.BACKWARD
heading: Heading class from which new heading objects will be
instantiated
connect_with_document: if True, the newly created heading will be
connected with the document, otherwise not
Returns:
heading or None: New heading
"""
start, end = get_domobj_range(
content=self._content, position=position, direction=direction,
identify_fun=heading.identify_heading)
if start is None:
return None
if end is None:
end = len(self._content) - 1
document = self if connect_with_document else None
return heading.parse_heading_from_data(
self._content[start:end + 1], self.get_all_todo_states(),
document=document, orig_start=start)

View File

@@ -0,0 +1,502 @@
# -*- coding: utf-8 -*-
"""
dom object
~~~~~~~~~~
TODO: explain this :)
"""
import re
from orgmode.liborgmode.base import MultiPurposeList, flatten_list
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
try:
from collections import UserList
except:
from UserList import UserList
# breaking down tasks regex
REGEX_SUBTASK = re.compile(r'\[(\d*)/(\d*)\]')
REGEX_SUBTASK_PERCENT = re.compile(r'\[(\d*)%\]')
# heading regex
REGEX_HEADING = re.compile(
r'^(?P<level>\*+)(\s+(?P<title>.*?))?\s*(\s(?P<tags>:[\w_:@]+:))?$',
flags=re.U)
REGEX_TAG = re.compile(
r'^\s*((?P<title>[^\s]*?)\s+)?(?P<tags>:[\w_:@]+:)$',
flags=re.U)
REGEX_TODO = re.compile(r'^[^\s]*$')
# checkbox regex:
# - [ ] checkbox item
# - [X] checkbox item
# - [ ]
# - no status checkbox
UnOrderListType = [u'-', u'+', u'*']
OrderListType = [u'.', u')']
REGEX_CHECKBOX = re.compile(
r'^(?P<level>\s*)(?P<type>[%s]|([a-zA-Z]|[\d]+)[%s])(\s+(?P<status>\[.\]))?\s*(?P<title>.*)$'
% (''.join(UnOrderListType), ''.join(OrderListType)), flags=re.U)
class DomObj(object):
u"""
A DomObj is DOM structure element, like Heading and Checkbox.
Its purpose is to abstract the same parts of Heading and Checkbox objects,
and make code reusable.
All methods and properties are extracted from Heading object.
Heading and Checkbox objects inherit from DomObj, and override some specific
methods in their own objects.
Normally, we don't intend to use DomObj directly. However, we can add some more
DOM structure element based on this class to make code more concise.
"""
# TODO should this and DomObj_list be abstract methods? If so use ABC to
# force abstract methods
def __init__(self, level=1, title=u'', body=None):
u"""
:level: Level of the dom object
:title: Title of the dom object
:body: Body of the dom object
"""
object.__init__(self)
self._document = None
self._parent = None
self._previous_sibling = None
self._next_sibling = None
self._children = MultiPurposeList()
self._orig_start = None
self._orig_len = 0
self._level = level
# title
self._title = u''
if title:
self.title = title
# body
self._dirty_body = False
self._body = MultiPurposeList(on_change=self.set_dirty_body)
if body:
self.body = body
def __unicode__(self):
return u'<dom obj level=%s, title=%s>' % (level, title)
def __str__(self):
return u_encode(self.__unicode__())
def __len__(self):
# 1 is for the heading's title
return 1 + len(self.body)
@property
def is_dirty(self):
u""" Return True if the dom obj body is marked dirty """
return self._dirty_body
@property
def is_dirty_body(self):
u""" Return True if the dom obj body is marked dirty """
return self._dirty_body
def get_index_in_parent_list(self):
""" Retrieve the index value of current dom obj in the parents list of
dom objs. This works also for top level dom objs.
:returns: Index value or None if dom obj doesn't have a
parent/document or is not in the list of dom objs
"""
l = self.get_parent_list()
if l:
return l.index(self)
def get_parent_list(self):
""" Retrieve the parents list of dom objs. This works also for top
level dom objs.
:returns: List of dom objs or None if dom objs doesn't have a
parent/document or is not in the list of dom objs
"""
if self.parent:
if self in self.parent.children:
return self.parent.children
def set_dirty(self):
u""" Mark the dom objs and body dirty so that it will be rewritten when
saving the document """
if self._document:
self._document.set_dirty_document()
def set_dirty_body(self):
u""" Mark the dom objs' body dirty so that it will be rewritten when
saving the document """
self._dirty_body = True
if self._document:
self._document.set_dirty_document()
@property
def document(self):
u""" Read only access to the document. If you want to change the
document, just assign the dom obj to another document """
return self._document
@property
def parent(self):
u""" Access to the parent dom obj """
return self._parent
@property
def number_of_parents(self):
u""" Access to the number of parent dom objs before reaching the root
document """
def count_parents(h):
if h.parent:
return 1 + count_parents(h.parent)
else:
return 0
return count_parents(self)
@property
def previous_sibling(self):
u""" Access to the previous dom obj that's a sibling of the current one
"""
return self._previous_sibling
@property
def next_sibling(self):
u""" Access to the next dom obj that's a sibling of the current one """
return self._next_sibling
@property
def previous_item(self):
u""" Serialized access to the previous dom obj """
if self.previous_sibling:
h = self.previous_sibling
while h.children:
h = h.children[-1]
return h
elif self.parent:
return self.parent
@property
def next_item(self):
u""" Serialized access to the next dom obj """
if self.children:
return self.children[0]
elif self.next_sibling:
return self.next_sibling
else:
h = self.parent
while h:
if h.next_sibling:
return h.next_sibling
else:
h = h.parent
@property
def start(self):
u""" Access to the starting line of the dom obj """
if self.document is None or not self.document.is_dirty:
return self._orig_start
def item_len_generator(h):
while h:
yield len(h)
h = h.previous_item
return sum(item for item in item_len_generator(self.previous_item))
@property
def start_vim(self):
if self.start is not None:
return self.start + 1
@property
def end(self):
u""" Access to the ending line of the dom obj """
if self.start is not None:
return self.start + len(self.body)
@property
def end_vim(self):
if self.end is not None:
return self.end + 1
@property
def end_of_last_child(self):
u""" Access to end of the last child """
if self.children:
child = self.children[-1]
while child.children:
child = child.children[-1]
return child.end
return self.end
@property
def end_of_last_child_vim(self):
return self.end_of_last_child + 1
@property
def children(self):
u""" MultiPurposeList[dom_objects??]: subheadings of the current DomObj
Setter method takes list, tuple or userlist with DOMObjects
"""
return self._children
@children.setter
def children(self, value):
v = value
if type(v) in (list, tuple) or isinstance(v, UserList):
v = flatten_list(v)
self._children[:] = v
@children.deleter
def children(self):
del self.children[:]
@property
def first_child(self):
u""" Access to the first child dom obj or None if no children exist """
if self.children:
return self.children[0]
@property
def last_child(self):
u""" Access to the last child dom obj or None if no children exist """
if self.children:
return self.children[-1]
@property
def level(self):
u""" int: Access the the dom obj level
Setter sets the DOM object and the document as dirty if invoked.
"""
return self._level
@level.setter
def level(self, value):
# TODO Shouldn't there be and error when values is not int?
self._level = int(value)
self.set_dirty()
@level.deleter
def level(self):
self.level = None
@property
def title(self):
u""" str: Get the title of current dom object
Setter sets the DOM object and the document as dirty if invoked.
"""
return self._title.strip()
@title.setter
def title(self, value):
if type(value) not in (unicode, str):
raise ValueError(u'Title must be a string.')
v = value
if type(v) == str:
v = u_decode(v)
self._title = v.strip()
self.set_dirty()
@title.deleter
def title(self):
self._title = u''
@property
def body(self):
u""" MultiPurposeList[]: Holds the content belonging to the heading """
return self._body
@body.setter
def body(self, value):
if type(value) in (list, tuple) or isinstance(value, UserList):
self._body[:] = flatten_list(value)
elif type(value) in (str, ):
self._body[:] = u_decode(value).split(u'\n')
elif type(value) in (unicode, ):
self._body[:] = value.split(u'\n')
else:
self.body = list(unicode(value))
@body.deleter
def body(self):
# TODO write this as del self._body[:] because there is no reason to
# call so much code for deleting a list
self.body = []
class DomObjList(MultiPurposeList):
u"""
A Dom Obj List
"""
def __init__(self, initlist=None, obj=None):
"""
:initlist: Initial data
:obj: Link to a concrete Heading or Document object
"""
# it's not necessary to register a on_change hook because the heading
# list will itself take care of marking headings dirty or adding
# headings to the deleted headings list
MultiPurposeList.__init__(self)
self._obj = obj
# initialization must be done here, because
# self._document is not initialized when the
# constructor of MultiPurposeList is called
if initlist:
self.extend(initlist)
@classmethod
def is_domobj(cls, obj):
# TODO no reason for it to be class method. Does it even need to exist
# because it is quite clear what isinstance does and in derived methods
# isinstance(Heading, DomObj) would return True anyway.
return isinstance(obj, DomObj)
# TODO this should be made into a property
def _get_document(self):
if self.__class__.is_domobj(self._obj):
return self._obj._document
return self._obj
def __setitem__(self, i, item):
if isinstance(i, slice):
o = item
if self.__class__.is_domobj(o):
o = (o, )
o = flatten_list(o)
for item in o:
if not self.__class__.is_domobj(item):
raise ValueError(u'List contains items that are not a Dom obj!')
# self._add_to_deleted_domobjs(self[i:j])
# self._associate_domobj(o, \
# self[i - 1] if i - 1 >= 0 and i < len(self) else None, \
# self[j] if j >= 0 and j < len(self) else None)
MultiPurposeList.__setitem__(self, i, o)
else:
if not self.__class__.is_domobj(item):
raise ValueError(u'Item is not a Dom obj!')
if item in self:
raise ValueError(u'Dom obj is already part of this list!')
# self._add_to_deleted_domobjs(self[i])
# self._associate_domobj(item, \
# self[i - 1] if i - 1 >= 0 else None, \
# self[i + 1] if i + 1 < len(self) else None)
MultiPurposeList.__setitem__(self, i, item)
def __delitem__(self, i, taint=True):
if isinstance(i, slice):
items = self[i]
if items:
first = items[0]
last = items[-1]
if first.previous_sibling:
first.previous_sibling._next_sibling = last.next_sibling
if last.next_sibling:
last.next_sibling._previous_sibling = first.previous_sibling
# if taint:
# self._add_to_deleted_domobjs(items)
else:
item = self[i]
if item.previous_sibling:
item.previous_sibling._next_sibling = item.next_sibling
if item.next_sibling:
item.next_sibling._previous_sibling = item.previous_sibling
# if taint:
# self._add_to_deleted_domobjs(item)
MultiPurposeList.__delitem__(self, i)
def __setslice__(self, i, j, other):
self.__setitem__(slice(i, j), other)
def __delslice__(self, i, j, taint=True):
self.__delitem__(slice(i, j), taint=taint)
def __iadd__(self, other):
o = other
if self.__class__.is_domobj(o):
o = (o, )
for item in flatten_list(o):
if not self.__class__.is_domobj(item):
raise ValueError(u'List contains items that are not a Dom obj!')
# self._associate_domobj(o, self[-1] if len(self) > 0 else None, None)
return MultiPurposeList.__iadd__(self, o)
def __imul__(self, n):
# TODO das müsste eigentlich ein klonen von objekten zur Folge haben
return MultiPurposeList.__imul__(self, n)
def append(self, item, taint=True):
if not self.__class__.is_domobj(item):
raise ValueError(u'Item is not a heading!')
if item in self:
raise ValueError(u'Heading is already part of this list!')
# self._associate_domobj(
# item, self[-1] if len(self) > 0 else None,
# None, taint=taint)
MultiPurposeList.append(self, item)
def insert(self, i, item, taint=True):
# self._associate_domobj(
# item,
# self[i - 1] if i - 1 >= 0 and i - 1 < len(self) else None,
# self[i] if i >= 0 and i < len(self) else None, taint=taint)
MultiPurposeList.insert(self, i, item)
def pop(self, i=-1):
item = self[i]
# self._add_to_deleted_domobjs(item)
del self[i]
return item
def remove_slice(self, i, j, taint=True):
self.__delitem__(slice(i, j), taint=taint)
def remove(self, item, taint=True):
self.__delitem__(self.index(item), taint=taint)
def reverse(self):
MultiPurposeList.reverse(self)
prev_h = None
for h in self:
h._previous_sibling = prev_h
h._next_sibling = None
prev_h._next_sibling = h
h.set_dirty()
prev_h = h
def sort(self, *args, **kwds):
MultiPurposeList.sort(*args, **kwds)
prev_h = None
for h in self:
h._previous_sibling = prev_h
h._next_sibling = None
prev_h._next_sibling = h
h.set_dirty()
prev_h = h
def extend(self, other):
o = other
if self.__class__.is_domobj(o):
o = (o, )
for item in o:
if not self.__class__.is_domobj(item):
raise ValueError(u'List contains items that are not a heading!')
# self._associate_domobj(o, self[-1] if len(self) > 0 else None, None)
MultiPurposeList.extend(self, o)

View File

@@ -0,0 +1,886 @@
# -*- coding: utf-8 -*-
"""
headings
~~~~~~~~~
TODO: explain this :)
"""
import re
import vim
from orgmode.liborgmode.base import MultiPurposeList, flatten_list, Direction, get_domobj_range
from orgmode.liborgmode.orgdate import OrgTimeRange
from orgmode.liborgmode.orgdate import get_orgdate
from orgmode.liborgmode.checkboxes import Checkbox, CheckboxList
from orgmode.liborgmode.dom_obj import DomObj, DomObjList, REGEX_SUBTASK, REGEX_SUBTASK_PERCENT, REGEX_HEADING, REGEX_TAG, REGEX_TODO
from orgmode.py3compat.xrange_compatibility import *
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
try:
from collections import UserList
except:
from UserList import UserList
from itertools import ifilter as filter
class Heading(DomObj):
u""" Structural heading object """
def __init__(self, level=1, title=u'', tags=None, todo=None, body=None, active_date=None):
u"""
:level: Level of the heading
:title: Title of the heading
:tags: Tags of the heading
:todo: Todo state of the heading
:body: Body of the heading
:active_date: active date that is used in the agenda
"""
DomObj.__init__(self, level=level, title=title, body=body)
self._children = HeadingList(obj=self)
self._dirty_heading = False
# todo
self._todo = None
if todo:
self.todo = todo
# tags
self._tags = MultiPurposeList(on_change=self.set_dirty_heading)
if tags:
self.tags = tags
# active date
self._active_date = active_date
if active_date:
self.active_date = active_date
# checkboxes
self._checkboxes = CheckboxList(obj=self)
self._cached_checkbox = None
def __unicode__(self):
res = u'*' * self.level
if self.todo:
res = u' '.join((res, self.todo))
if self.title:
res = u' '.join((res, self.title))
# compute position of tags
if self.tags:
tabs = 0
spaces = 2
tags = u':%s:' % (u':'.join(self.tags), )
# FIXME this is broken because of missing associations for headings
ts = 6
tag_column = 77
if self.document:
ts = self.document.tabstop
tag_column = self.document.tag_column
len_heading = len(res)
len_tags = len(tags)
if len_heading + spaces + len_tags < tag_column:
spaces_to_next_tabstop = ts - divmod(len_heading, ts)[1]
if len_heading + spaces_to_next_tabstop + len_tags < tag_column:
tabs, spaces = divmod(
tag_column - (len_heading + spaces_to_next_tabstop + len_tags),
ts)
if spaces_to_next_tabstop:
tabs += 1
else:
spaces = tag_column - (len_heading + len_tags)
res += u'\t' * tabs + u' ' * spaces + tags
# append a trailing space when there are just * and no text
if len(res) == self.level:
res += u' '
return res
def __str__(self):
return u_encode(self.__unicode__())
def __len__(self):
# 1 is for the heading's title
return 1 + len(self.body)
def __lt__(self, other):
"""
Headings can be sorted by date.
"""
try:
if self.active_date < other.active_date:
return True
elif self.active_date == other.active_date:
return False
elif self.active_date > other.active_date:
return False
except:
if self.active_date and not other.active_date:
return True
elif not self.active_date and other.active_date:
return False
elif not self.active_date and not other.active_date:
return False
def __le__(self, other):
"""
Headings can be sorted by date.
"""
try:
if self.active_date < other.active_date:
return True
elif self.active_date == other.active_date:
return True
elif self.active_date > other.active_date:
return False
except:
if self.active_date and not other.active_date:
return True
elif not self.active_date and other.active_date:
return False
elif not self.active_date and not other.active:
return True
def __ge__(self, other):
"""
Headings can be sorted by date.
"""
try:
if self.active_date > other.active_date:
return True
elif self.active_date == other.active_date:
return True
elif self.active_date < other.active_date:
return False
except:
if not self.active_date and other.active_date:
return True
elif self.active_date and not other.active_date:
return False
elif not self.active_date and not other.active:
return True
def __gt__(self, other):
"""
Headings can be sorted by date.
"""
try:
if self.active_date > other.active_date:
return True
elif self.active_date == other.active_date:
return False
elif self.active_date < other.active_date:
return False
except:
if not self.active_date and other.active_date:
return True
elif self.active_date and not other.active_date:
return False
elif not self.active_date and not other.active:
return False
def copy(self, including_children=True, parent=None):
u"""
Create a copy of the current heading. The heading will be completely
detached and not even belong to a document anymore.
:including_children: If True a copy of all children is create as
well. If False the returned heading doesn't
have any children.
:parent: Don't use this parameter. It's set
automatically.
"""
heading = self.__class__(
level=self.level, title=self.title,
tags=self.tags, todo=self.todo, body=self.body[:])
if parent:
parent.children.append(heading)
if including_children and self.children:
for item in self.children:
item.copy(
including_children=including_children,
parent=heading)
heading._orig_start = self._orig_start
heading._orig_len = self._orig_len
heading._dirty_heading = self.is_dirty_heading
return heading
def all_checkboxes(self):
u""" Iterate over all checkboxes of the current heading in serialized
order
:returns: Returns an iterator object which returns all checkboxes of
the current heading in serialized order
"""
if not self.checkboxes:
return
c = self.first_checkbox
while c:
yield c
c = c.next_checkbox
return
def all_toplevel_checkboxes(self):
u""" return all top level checkboxes for current heading """
if not self.checkboxes:
return
c = self.first_checkbox
while c:
yield c
c = c.next_sibling
return
def find_checkbox(self, position=0, direction=Direction.FORWARD,
checkbox=Checkbox, connect_with_heading=True):
u""" Find checkbox in the given direction
:position: starting line, counting from 0 (in vim you start
counting from 1, don't forget)
:direction: downwards == Direction.FORWARD,
upwards == Direction.BACKWARD
:checkbox: Checkbox class from which new checkbox objects will be
instantiated
:connect_with_heading: if True, the newly created checkbox will be
connected with the heading, otherwise not
:returns: New checkbox object or None
"""
doc = self.document
(start, end) = get_domobj_range(content=doc._content, position=position, direction=direction, identify_fun=checkbox.identify_checkbox)
# if out of current headinig range, return None
heading_end = self.start + len(self) - 1
if start is not None and start > heading_end:
return None
if end is not None and end > heading_end:
end = heading_end
if start is not None and end is None:
end = heading_end
if start is not None and end is not None:
return checkbox.parse_checkbox_from_data(
doc._content[start:end + 1],
heading=self if connect_with_heading else None, orig_start=start)
def init_checkboxes(self, checkbox=Checkbox):
u""" Initialize all checkboxes in current heading - build DOM.
:returns: self
"""
def init_checkbox(_c):
u"""
:returns the initialized checkbox
"""
start = _c.end + 1
prev_checkbox = None
while True:
new_checkbox = self.find_checkbox(start, checkbox=checkbox)
# * Checkbox 1 <- checkbox
# * Checkbox 1 <- sibling
# or
# * Checkbox 2 <- checkbox
# * Checkbox 1 <- parent's sibling
if not new_checkbox or \
new_checkbox.level <= _c.level:
break
# * Checkbox 1 <- heading
# * Checkbox 2 <- first child
# * Checkbox 2 <- another child
new_checkbox._parent = _c
if prev_checkbox:
prev_checkbox._next_sibling = new_checkbox
new_checkbox._previous_sibling = prev_checkbox
_c.children.data.append(new_checkbox)
# the start and end computation is only
# possible when the new checkbox was properly
# added to the document structure
init_checkbox(new_checkbox)
if new_checkbox.children:
# skip children
start = new_checkbox.end_of_last_child + 1
else:
start = new_checkbox.end + 1
prev_checkbox = new_checkbox
return _c
c = self.find_checkbox(checkbox=checkbox, position=self.start)
# initialize dom tree
prev_c = None
while c:
if prev_c and prev_c.level == c.level:
prev_c._next_sibling = c
c._previous_sibling = prev_c
self.checkboxes.data.append(c)
init_checkbox(c)
prev_c = c
c = self.find_checkbox(c.end_of_last_child + 1, checkbox=checkbox)
return self
def current_checkbox(self, position=None):
u""" Find the current checkbox (search backward) and return the related object
:returns: Checkbox object or None
"""
if position is None:
position = vim.current.window.cursor[0] - 1
if not self.checkboxes:
return
def binaryFindInHeading():
hi = len(self.checkboxes)
lo = 0
while lo < hi:
mid = (lo + hi) // 2
c = self.checkboxes[mid]
if c.end_of_last_child < position:
lo = mid + 1
elif c.start > position:
hi = mid
else:
return binaryFindCheckbox(c)
def binaryFindCheckbox(checkbox):
if not checkbox.children or checkbox.end >= position:
return checkbox
hi = len(checkbox.children)
lo = 0
while lo < hi:
mid = (lo + hi) // 2
c = checkbox.children[mid]
if c.end_of_last_child < position:
lo = mid + 1
elif c.start > position:
hi = mid
else:
return binaryFindCheckbox(c)
# look at the cache to find the heading
c_tmp = self._cached_checkbox
if c_tmp is not None:
if c_tmp.end_of_last_child > position and \
c_tmp.start < position:
if c_tmp.end < position:
self._cached_checkbox = binaryFindCheckbox(c_tmp)
return self._cached_checkbox
self._cached_checkbox = binaryFindInHeading()
return self._cached_checkbox
@property
def first_checkbox(self):
u""" Access to the first child checkbox or None if no children exist """
if self.checkboxes:
return self.checkboxes[0]
@classmethod
def parse_heading_from_data(
cls, data, allowed_todo_states, document=None,
orig_start=None):
u""" Construct a new heading from the provided data
:data: List of lines
:allowed_todo_states: TODO???
:document: The document object this heading belongs to
:orig_start: The original start of the heading in case it was read
from a document. If orig_start is provided, the
resulting heading will not be marked dirty.
:returns: The newly created heading
"""
test_not_empty = lambda x: x != u''
def parse_title(heading_line):
# WARNING this regular expression fails if there is just one or no
# word in the heading but a tag!
m = REGEX_HEADING.match(heading_line)
if m:
r = m.groupdict()
level = len(r[u'level'])
todo = None
title = u''
tags = filter(test_not_empty, r[u'tags'].split(u':')) if r[u'tags'] else []
tags = list(tags)
# if there is just one or no word in the heading, redo the parsing
mt = REGEX_TAG.match(r[u'title'])
if not tags and mt:
r = mt.groupdict()
tags = filter(test_not_empty, r[u'tags'].split(u':')) if r[u'tags'] else []
tags = list(tags)
if r[u'title'] is not None:
_todo_title = [i.strip() for i in r[u'title'].split(None, 1)]
if _todo_title and _todo_title[0] in allowed_todo_states:
todo = _todo_title[0]
if len(_todo_title) > 1:
title = _todo_title[1]
else:
title = r[u'title'].strip()
return (level, todo, title, tags)
raise ValueError(u'Data doesn\'t start with a heading definition.')
if not data:
raise ValueError(u'Unable to create heading, no data provided.')
# create new heaing
new_heading = cls()
new_heading.level, new_heading.todo, new_heading.title, new_heading.tags = parse_title(data[0])
new_heading.body = data[1:]
if orig_start is not None:
new_heading._dirty_heading = False
new_heading._dirty_body = False
new_heading._orig_start = orig_start
new_heading._orig_len = len(new_heading)
if document:
new_heading._document = document
# try to find active dates
tmp_orgdate = get_orgdate(data)
if tmp_orgdate and tmp_orgdate.active \
and not isinstance(tmp_orgdate, OrgTimeRange):
new_heading.active_date = tmp_orgdate
else:
new_heading.active_date = None
return new_heading
def update_subtasks(self, total=0, on=0):
u""" Update subtask information for current heading
:total: total # of top level checkboxes
:on: # of top level checkboxes which are on
"""
if total != 0:
percent = (on * 100) / total
else:
percent = 0
count = "%d/%d" % (on, total)
self.title = REGEX_SUBTASK.sub("[%s]" % (count), self.title)
self.title = REGEX_SUBTASK_PERCENT.sub("[%d%%]" % (percent), self.title)
self.document.write_heading(self, including_children=False)
@staticmethod
def identify_heading(line):
u""" Test if a certain line is a heading or not.
Args:
line (str): the line to check
Returns:
int or None: level of heading or None if line is not heading
"""
# TODO would it make sense to return 0 for heading level?
# TODO add tests e.g. '*** abc', '**', '', '* ', '*\t', '*'
for i, item in enumerate(line):
if item == '*':
continue
elif i and item in ('\t', ' '):
return i
break
return None
@property
def is_dirty(self):
u""" Return True if the heading's body is marked dirty """
return self._dirty_heading or self._dirty_body
@property
def is_dirty_heading(self):
u""" Return True if the heading is marked dirty """
return self._dirty_heading
def get_index_in_parent_list(self):
""" Retrieve the index value of current heading in the parents list of
headings. This works also for top level headings.
:returns: Index value or None if heading doesn't have a
parent/document or is not in the list of headings
"""
if self.parent:
return super(Heading, self).get_index_in_parent_list()
elif self.document:
l = self.get_parent_list()
if l:
return l.index(self)
def get_parent_list(self):
""" Retrieve the parents' list of headings. This works also for top
level headings.
:returns: List of headings or None if heading doesn't have a
parent/document or is not in the list of headings
"""
if self.parent:
return super(Heading, self).get_parent_list()
elif self.document:
if self in self.document.headings:
return self.document.headings
def set_dirty(self):
u""" Mark the heading and body dirty so that it will be rewritten when
saving the document """
self._dirty_heading = True
self._dirty_body = True
if self._document:
self._document.set_dirty_document()
def set_dirty_heading(self):
u""" Mark the heading dirty so that it will be rewritten when saving the
document """
self._dirty_heading = True
if self._document:
self._document.set_dirty_document()
@property
def previous_heading(self):
u""" Serialized access to the previous heading """
return super(Heading, self).previous_item
@property
def next_heading(self):
u""" Serialized access to the next heading """
return super(Heading, self).next_item
@property
def start(self):
u""" Access to the starting line of the heading """
if self.document is None or not self.document.is_dirty:
return self._orig_start
meta_len = len(self.document.meta_information) if \
self.document.meta_information else 0
return super(Heading, self).start + meta_len
@DomObj.level.setter
def level(self, value):
u""" Set the heading level and mark the heading and the document dirty """
self._level = int(value)
self.set_dirty_heading()
@property
def todo(self):
u""" Todo state of current heading. When todo state is set"""
# extract todo state from heading
return self._todo
@todo.setter
def todo(self, value):
# update todo state
if type(value) not in (unicode, str, type(None)):
raise ValueError(u'Todo state must be a string or None.')
if value and not REGEX_TODO.match(value):
raise ValueError(u'Found non allowed character in todo state! %s' % value)
if not value:
self._todo = None
else:
v = value
if type(v) == str:
v = u_decode(v)
self._todo = v
self.set_dirty_heading()
@todo.deleter
def todo(self):
self.todo = None
@property
def active_date(self):
u"""
active date of the hearing.
active dates are used in the agenda view. they can be part of the
heading and/or the body.
"""
return self._active_date
@active_date.setter
def active_date(self, value):
self._active_date = value
@active_date.deleter
def active_date(self):
self._active_date = None
@DomObj.title.setter
def title(self, value):
u""" Set the title and mark the document and the heading dirty """
# TODO these setter should be rewritten to also reuse code from DOM OBJ
if type(value) not in (unicode, str):
raise ValueError(u'Title must be a string.')
v = value
if type(v) == str:
v = u_decode(v)
self._title = v.strip()
self.set_dirty_heading()
@property
def tags(self):
u""" Tags of the current heading """
return self._tags
@tags.setter
def tags(self, value):
v = value
if type(v) in (unicode, str):
v = list(unicode(v))
if type(v) not in (list, tuple) and not isinstance(v, UserList):
v = list(unicode(v))
v = flatten_list(v)
v_decoded = []
for i in v:
if type(i) not in (unicode, str):
raise ValueError(u'Found non string value in tags! %s' % unicode(i))
if u':' in i:
raise ValueError(u'Found non allowed character in tag! %s' % i)
i_tmp = i.strip().replace(' ', '_').replace('\t', '_')
if type(i) == str:
i_tmp = u_decode(i)
v_decoded.append(i_tmp)
self._tags[:] = v_decoded
@tags.deleter
def tags(self):
self.tags = []
@property
def checkboxes(self):
u""" All checkboxes in current heading """
return self._checkboxes
@checkboxes.setter
def checkboxes(self, value):
self._checkboxes[:] = value
@checkboxes.deleter
def checkboxes(self):
del self.checkboxes[:]
class HeadingList(DomObjList):
u"""
A Heading List just contains headings. It's used for documents to store top
level headings and for headings to store subheadings.
A Heading List must be linked to a Document or Heading!
See documentation of MultiPurposeList for more information.
"""
def __init__(self, initlist=None, obj=None):
"""
:initlist: Initial data
:obj: Link to a concrete Heading or Document object
"""
# it's not necessary to register a on_change hook because the heading
# list will itself take care of marking headings dirty or adding
# headings to the deleted headings list
DomObjList.__init__(self, initlist, obj)
@classmethod
def is_heading(cls, obj):
# TODO no need to make this or is_domobj a class methods
return HeadingList.is_domobj(obj)
def _get_document(self):
if self.__class__.is_heading(self._obj):
return self._obj._document
return self._obj
def _add_to_deleted_headings(self, item):
u"""
Serialize headings so that all subheadings are also marked for deletion
"""
if not self._get_document():
# HeadingList has not yet been associated
return
if type(item) in (list, tuple) or isinstance(item, UserList):
for i in flatten_list(item):
self._add_to_deleted_headings(i)
else:
self._get_document()._deleted_headings.append(
item.copy(including_children=False))
self._add_to_deleted_headings(item.children)
self._get_document().set_dirty_document()
def _associate_heading(
self, heading, previous_sibling, next_sibling,
children=False, taint=True):
"""
:heading: The heading or list to associate with the current heading
:previous_sibling: The previous sibling of the current heading. If
heading is a list the first heading will be
connected with the previous sibling and the last
heading with the next sibling. The items in between
will be linked with one another.
:next_sibling: The next sibling of the current heading. If
heading is a list the first heading will be
connected with the previous sibling and the last
heading with the next sibling. The items in between
will be linked with one another.
:children: Marks whether children are processed in the current
iteration or not (should not be use, it's set
automatically)
:taint: If not True, the heading is not marked dirty at the end
of the association process and its orig_start and
orig_len values are not updated.
"""
# TODO this method should be externalized and moved to the Heading class
# TODO should this method work with slice?
if type(heading) in (list, tuple) or isinstance(heading, UserList):
prev = previous_sibling
current = None
for _next in flatten_list(heading):
if current:
self._associate_heading(
current, prev, _next,
children=children, taint=taint)
prev = current
current = _next
if current:
self._associate_heading(
current, prev, next_sibling,
children=children, taint=taint)
else:
if taint:
heading._orig_start = None
heading._orig_len = None
d = self._get_document()
if heading._document != d:
heading._document = d
if not children:
# connect heading with previous and next headings
heading._previous_sibling = previous_sibling
if previous_sibling:
previous_sibling._next_sibling = heading
heading._next_sibling = next_sibling
if next_sibling:
next_sibling._previous_sibling = heading
if d == self._obj:
# self._obj is a Document
heading._parent = None
elif heading._parent != self._obj:
# self._obj is a Heading
heading._parent = self._obj
if taint:
heading.set_dirty()
self._associate_heading(
heading.children, None, None,
children=True, taint=taint)
def __setitem__(self, i, item):
if isinstance(i, slice):
start, stop, step = i.indices(len(self))
items = item
if self.__class__.is_heading(items):
items = (items, )
items = flatten_list(items)
for head in items:
if not self.__class__.is_heading(head):
raise ValueError(u'List contains items that are not a heading!')
self._add_to_deleted_headings(self[i])
self._associate_heading(
items,
self[start - 1] if start - 1 >= 0 else None,
self[stop] if stop < len(self) else None)
MultiPurposeList.__setitem__(self, i, items)
else:
if not self.__class__.is_heading(item):
raise ValueError(u'Item is not a heading!')
if item in self:
raise ValueError(u'Heading is already part of this list!')
self._add_to_deleted_headings(self[i])
self._associate_heading(
item,
self[i - 1] if i - 1 >= 0 else None,
self[i + 1] if i + 1 < len(self) else None)
MultiPurposeList.__setitem__(self, i, item)
def __delitem__(self, i, taint=True):
# TODO refactor this item, it works the same in dom_obj except taint?
if isinstance(i, slice):
items = self[i]
if items:
first = items[0]
last = items[-1]
if first.previous_sibling:
first.previous_sibling._next_sibling = last.next_sibling
if last.next_sibling:
last.next_sibling._previous_sibling = first.previous_sibling
if taint:
self._add_to_deleted_headings(items)
MultiPurposeList.__delitem__(self, i)
else:
item = self[i]
if item.previous_sibling:
item.previous_sibling._next_sibling = item.next_sibling
if item.next_sibling:
item.next_sibling._previous_sibling = item.previous_sibling
if taint:
self._add_to_deleted_headings(item)
MultiPurposeList.__delitem__(self, i)
def __iadd__(self, other):
o = other
if self.__class__.is_heading(o):
o = (o, )
for item in flatten_list(o):
if not self.__class__.is_heading(item):
raise ValueError(u'List contains items that are not a heading!')
self._associate_heading(o, self[-1] if len(self) > 0 else None, None)
return MultiPurposeList.__iadd__(self, o)
def append(self, item, taint=True):
if not self.__class__.is_heading(item):
raise ValueError(u'Item is not a heading!')
if item in self:
raise ValueError(u'Heading is already part of this list!')
self._associate_heading(
item, self[-1] if len(self) > 0 else None,
None, taint=taint)
MultiPurposeList.append(self, item)
def insert(self, i, item, taint=True):
self._associate_heading(
item,
self[i - 1] if i - 1 >= 0 and i - 1 < len(self) else None,
self[i] if i >= 0 and i < len(self) else None, taint=taint)
MultiPurposeList.insert(self, i, item)
def pop(self, i=-1):
item = self[i]
self._add_to_deleted_headings(item)
del self[i]
return item
def extend(self, other):
o = other
if self.__class__.is_heading(o):
o = (o, )
for item in o:
if not self.__class__.is_heading(item):
raise ValueError(u'List contains items that are not a heading!')
self._associate_heading(o, self[-1] if len(self) > 0 else None, None)
MultiPurposeList.extend(self, o)

View File

@@ -0,0 +1,294 @@
# -*- coding: utf-8 -*-
u"""
OrgDate
~~~~~~~~~~~~~~~~~~
This module contains all date/time/timerange representations that exist in
orgmode.
There exist three different kinds:
* OrgDate: is similar to a date object in python and it looks like
'2011-09-07 Wed'.
* OrgDateTime: is similar to a datetime object in python and looks like
'2011-09-07 Wed 10:30'
* OrgTimeRange: indicates a range of time. It has a start and and end date:
* <2011-09-07 Wed>--<2011-09-08 Fri>
* <2011-09-07 Wed 10:00-13:00>
All OrgTime oblects can be active or inactive.
"""
import datetime
import re
from orgmode.py3compat.encode_compatibility import *
# <2011-09-12 Mon>
_DATE_REGEX = re.compile(r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w>", re.UNICODE)
# [2011-09-12 Mon]
_DATE_PASSIVE_REGEX = re.compile(r"\[(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w\]", re.UNICODE)
# <2011-09-12 Mon 10:20>
_DATETIME_REGEX = re.compile(
r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d{1,2}):(\d\d)>", re.UNICODE)
# [2011-09-12 Mon 10:20]
_DATETIME_PASSIVE_REGEX = re.compile(
r"\[(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d{1,2}):(\d\d)\]", re.UNICODE)
# <2011-09-12 Mon>--<2011-09-13 Tue>
_DATERANGE_REGEX = re.compile(
# <2011-09-12 Mon>--
r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w>--"
# <2011-09-13 Tue>
r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w>", re.UNICODE)
# <2011-09-12 Mon 10:00>--<2011-09-12 Mon 11:00>
_DATETIMERANGE_REGEX = re.compile(
# <2011-09-12 Mon 10:00>--
r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d\d):(\d\d)>--"
# <2011-09-12 Mon 11:00>
r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d\d):(\d\d)>", re.UNICODE)
# <2011-09-12 Mon 10:00--12:00>
_DATETIMERANGE_SAME_DAY_REGEX = re.compile(
r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d\d):(\d\d)-(\d\d):(\d\d)>", re.UNICODE)
def get_orgdate(data):
u"""
Parse the given data (can be a string or list). Return an OrgDate if data
contains a string representation of an OrgDate; otherwise return None.
data can be a string or a list containing strings.
"""
# TODO maybe it should be checked just for iterable? Does it affect here if
# in base __getitem__(slice(i,j)) doesn't return a list but userlist...
if isinstance(data, list):
return _findfirst(_text2orgdate, data)
else:
return _text2orgdate(data)
# if no dates found
return None
def _findfirst(f, seq):
u"""
Return first item in sequence seq where f(item) == True.
TODO: this is a general help function and it should be moved somewhere
else; preferably into the standard lib :)
"""
for found in (f(item) for item in seq if f(item)):
return found
def _text2orgdate(string):
u"""
Transform the given string into an OrgDate.
Return an OrgDate if data contains a string representation of an OrgDate;
otherwise return None.
"""
# handle active datetime with same day
result = _DATETIMERANGE_SAME_DAY_REGEX.search(string)
if result:
try:
(syear, smonth, sday, shour, smin, ehour, emin) = \
[int(m) for m in result.groups()]
start = datetime.datetime(syear, smonth, sday, shour, smin)
end = datetime.datetime(syear, smonth, sday, ehour, emin)
return OrgTimeRange(True, start, end)
except BaseException:
return None
# handle active datetime
result = _DATETIMERANGE_REGEX.search(string)
if result:
try:
tmp = [int(m) for m in result.groups()]
(syear, smonth, sday, shour, smin, eyear, emonth, eday, ehour, emin) = tmp
start = datetime.datetime(syear, smonth, sday, shour, smin)
end = datetime.datetime(eyear, emonth, eday, ehour, emin)
return OrgTimeRange(True, start, end)
except BaseException:
return None
# handle active datetime
result = _DATERANGE_REGEX.search(string)
if result:
try:
tmp = [int(m) for m in result.groups()]
syear, smonth, sday, eyear, emonth, ehour = tmp
start = datetime.date(syear, smonth, sday)
end = datetime.date(eyear, emonth, ehour)
return OrgTimeRange(True, start, end)
except BaseException:
return None
# handle active datetime
result = _DATETIME_REGEX.search(string)
if result:
try:
year, month, day, hour, minutes = [int(m) for m in result.groups()]
return OrgDateTime(True, year, month, day, hour, minutes)
except BaseException:
return None
# handle passive datetime
result = _DATETIME_PASSIVE_REGEX.search(string)
if result:
try:
year, month, day, hour, minutes = [int(m) for m in result.groups()]
return OrgDateTime(False, year, month, day, hour, minutes)
except BaseException:
return None
# handle passive dates
result = _DATE_PASSIVE_REGEX.search(string)
if result:
try:
year, month, day = [int(m) for m in result.groups()]
return OrgDate(False, year, month, day)
except BaseException:
return None
# handle active dates
result = _DATE_REGEX.search(string)
if result:
try:
year, month, day = [int(m) for m in result.groups()]
return OrgDate(True, year, month, day)
except BaseException:
return None
class OrgDate(datetime.date):
u"""
OrgDate represents a normal date like '2011-08-29 Mon'.
OrgDates can be active or inactive.
NOTE: date is immutable. That's why there needs to be __new__().
See: http://docs.python.org/reference/datamodel.html#object.__new__
"""
def __init__(self, active, year, month, day):
self.active = active
pass
def __new__(cls, active, year, month, day):
return datetime.date.__new__(cls, year, month, day)
def __unicode__(self):
u"""
Return a string representation.
"""
if self.active:
return self.strftime(u'<%Y-%m-%d %a>')
else:
return self.strftime(u'[%Y-%m-%d %a]')
def __str__(self):
return u_encode(self.__unicode__())
def strftime(self, fmt):
return u_decode(datetime.date.strftime(self, u_encode(fmt)))
class OrgDateTime(datetime.datetime):
u"""
OrgDateTime represents a normal date like '2011-08-29 Mon'.
OrgDateTime can be active or inactive.
NOTE: date is immutable. That's why there needs to be __new__().
See: http://docs.python.org/reference/datamodel.html#object.__new__
"""
def __init__(self, active, year, month, day, hour, mins):
self.active = active
def __new__(cls, active, year, month, day, hour, minute):
return datetime.datetime.__new__(cls, year, month, day, hour, minute)
def __unicode__(self):
u"""
Return a string representation.
"""
if self.active:
return self.strftime(u'<%Y-%m-%d %a %H:%M>')
else:
return self.strftime(u'[%Y-%m-%d %a %H:%M]')
def __str__(self):
return u_encode(self.__unicode__())
def strftime(self, fmt):
return u_decode(datetime.datetime.strftime(self, u_encode(fmt)))
class OrgTimeRange(object):
u"""
OrgTimeRange objects have a start and an end. Start and ent can be date
or datetime. Start and end have to be the same type.
OrgTimeRange objects look like this:
* <2011-09-07 Wed>--<2011-09-08 Fri>
* <2011-09-07 Wed 20:00>--<2011-09-08 Fri 10:00>
* <2011-09-07 Wed 10:00-13:00>
"""
def __init__(self, active, start, end):
u"""
stat and end must be datetime.date or datetime.datetime (both of the
same type).
"""
super(OrgTimeRange, self).__init__()
self.start = start
self.end = end
self.active = active
def __unicode__(self):
u"""
Return a string representation.
"""
# active
if self.active:
# datetime
if isinstance(self.start, datetime.datetime):
# if start and end are on same the day
if self.start.year == self.end.year and\
self.start.month == self.end.month and\
self.start.day == self.end.day:
return u"<%s-%s>" % (
self.start.strftime(u'%Y-%m-%d %a %H:%M'),
self.end.strftime(u'%H:%M'))
else:
return u"<%s>--<%s>" % (
self.start.strftime(u'%Y-%m-%d %a %H:%M'),
self.end.strftime(u'%Y-%m-%d %a %H:%M'))
# date
if isinstance(self.start, datetime.date):
return u"<%s>--<%s>" % (
self.start.strftime(u'%Y-%m-%d %a'),
self.end.strftime(u'%Y-%m-%d %a'))
# inactive
else:
if isinstance(self.start, datetime.datetime):
# if start and end are on same the day
if self.start.year == self.end.year and\
self.start.month == self.end.month and\
self.start.day == self.end.day:
return u"[%s-%s]" % (
self.start.strftime(u'%Y-%m-%d %a %H:%M'),
self.end.strftime(u'%H:%M'))
else:
return u"[%s]--[%s]" % (
self.start.strftime(u'%Y-%m-%d %a %H:%M'),
self.end.strftime(u'%Y-%m-%d %a %H:%M'))
if isinstance(self.start, datetime.date):
return u"[%s]--[%s]" % (
self.start.strftime(u'%Y-%m-%d %a'),
self.end.strftime(u'%Y-%m-%d %a'))
def __str__(self):
return u_encode(self.__unicode__())

View File

@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
import vim
from orgmode.keybinding import Command, Plug, Keybinding
from orgmode.keybinding import MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT
from orgmode.py3compat.encode_compatibility import *
def register_menu(f):
def r(*args, **kwargs):
p = f(*args, **kwargs)
def create(entry):
if isinstance(entry, Submenu) or isinstance(entry, Separator) \
or isinstance(entry, ActionEntry):
entry.create()
if hasattr(p, u'menu'):
if isinstance(p.menu, list) or isinstance(p.menu, tuple):
for e in p.menu:
create(e)
else:
create(p.menu)
return p
return r
def add_cmd_mapping_menu(plugin, name, function, key_mapping, menu_desrc):
u"""A helper function to create a vim command and keybinding and add these
to the menu for a given plugin.
:plugin: the plugin to operate on.
:name: the name of the vim command (and the name of the Plug)
:function: the actual python function which is called when executing the
vim command.
:key_mapping: the keymapping to execute the command.
:menu_desrc: the text which appears in the menu.
"""
cmd = Command(name, function)
keybinding = Keybinding(key_mapping, Plug(name, cmd))
plugin.commands.append(cmd)
plugin.keybindings.append(keybinding)
plugin.menu + ActionEntry(menu_desrc, keybinding)
class Submenu(object):
u""" Submenu entry """
def __init__(self, name, parent=None):
object.__init__(self)
self.name = name
self.parent = parent
self._children = []
def __add__(self, entry):
if entry not in self._children:
self._children.append(entry)
entry.parent = self
return entry
def __sub__(self, entry):
if entry in self._children:
idx = self._children.index(entry)
del self._children[idx]
@property
def children(self):
return self._children[:]
def get_menu(self):
n = self.name.replace(u' ', u'\\ ')
if self.parent:
return u'%s.%s' % (self.parent.get_menu(), n)
return n
def create(self):
for c in self.children:
c.create()
def __str__(self):
res = self.name
for c in self.children:
res += str(c)
return res
class Separator(object):
u""" Menu entry for a Separator """
def __init__(self, parent=None):
object.__init__(self)
self.parent = parent
def __unicode__(self):
return u'-----'
def __str__(self):
return u_encode(self.__unicode__())
def create(self):
if self.parent:
menu = self.parent.get_menu()
vim.command(u_encode(u'menu %s.-%s- :' % (menu, id(self))))
class ActionEntry(object):
u""" ActionEntry entry """
def __init__(self, lname, action, rname=None, mode=MODE_NORMAL, parent=None):
u"""
:lname: menu title on the left hand side of the menu entry
:action: could be a vim command sequence or an actual Keybinding
:rname: menu title that appears on the right hand side of the menu
entry. If action is a Keybinding this value ignored and is
taken from the Keybinding
:mode: defines when the menu entry/action is executable
:parent: the parent instance of this object. The only valid parent is Submenu
"""
object.__init__(self)
self._lname = lname
self._action = action
self._rname = rname
if mode not in (MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT):
raise ValueError(u'Parameter mode not in MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT')
self._mode = mode
self.parent = parent
def __str__(self):
return u'%s\t%s' % (self.lname, self.rname)
@property
def lname(self):
return self._lname.replace(u' ', u'\\ ')
@property
def action(self):
if isinstance(self._action, Keybinding):
return self._action.action
return self._action
@property
def rname(self):
if isinstance(self._action, Keybinding):
return self._action.key.replace(u'<Tab>', u'Tab')
return self._rname
@property
def mode(self):
if isinstance(self._action, Keybinding):
return self._action.mode
return self._mode
def create(self):
menucmd = u':%smenu ' % self.mode
menu = u''
cmd = u''
if self.parent:
menu = self.parent.get_menu()
menu += u'.%s' % self.lname
if self.rname:
cmd = u'%s %s<Tab>%s %s' % (menucmd, menu, self.rname, self.action)
else:
cmd = u'%s %s %s' % (menucmd, menu, self.action)
vim.command(u_encode(cmd))
# keybindings should be stored in the plugin.keybindings property and be registered by the appropriate keybinding registrar
#if isinstance(self._action, Keybinding):
# self._action.create()

View File

@@ -0,0 +1,312 @@
# -*- coding: utf-8 -*-
from datetime import date
import os
import glob
import vim
from orgmode._vim import ORGMODE, get_bufnumber, get_bufname, echoe
from orgmode import settings
from orgmode.keybinding import Keybinding, Plug, Command
from orgmode.menu import Submenu, ActionEntry, add_cmd_mapping_menu
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
from orgmode.py3compat.py_py3_string import *
class Agenda(object):
u"""
The Agenda Plugin uses liborgmode.agenda to display the agenda views.
The main task is to format the agenda from liborgmode.agenda.
Also all the mappings: jump from agenda to todo, etc are realized here.
"""
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'Agenda')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
# commands for this plugin
self.commands = []
@classmethod
def _switch_to(cls, bufname, vim_commands=None):
u"""
Swicht to the buffer with bufname.
A list of vim.commands (if given) gets executed as well.
TODO: this should be extracted and imporved to create an easy to use
way to create buffers/jump to buffers. Otherwise there are going to be
quite a few ways to open buffers in vimorgmode.
"""
cmds = [
u'botright split org:%s' % bufname,
u'setlocal buftype=nofile',
u'setlocal modifiable',
u'setlocal nonumber',
# call opendoc() on enter the original todo item
u'nnoremap <silent> <buffer> <CR> :exec "%s ORGMODE.plugins[u\'Agenda\'].opendoc()"<CR>' % VIM_PY_CALL,
u'nnoremap <silent> <buffer> <TAB> :exec "%s ORGMODE.plugins[u\'Agenda\'].opendoc(switch=True)"<CR>' % VIM_PY_CALL,
u'nnoremap <silent> <buffer> <S-CR> :exec "%s ORGMODE.plugins[u\'Agenda\'].opendoc(split=True)"<CR>' % VIM_PY_CALL,
# statusline
u'setlocal statusline=Org\\ %s' % bufname]
if vim_commands:
cmds.extend(vim_commands)
for cmd in cmds:
vim.command(u_encode(cmd))
@classmethod
def _get_agendadocuments(self):
u"""
Return the org documents of the agenda files; return None if no
agenda documents are defined.
TODO: maybe turn this into an decorator?
"""
# load org files of agenda
agenda_files = settings.get(u'org_agenda_files', u',')
if not agenda_files or agenda_files == ',':
echoe(
u"No org_agenda_files defined. Use :let "
u"g:org_agenda_files=['~/org/index.org'] to add "
u"files to the agenda view.")
return
return self._load_agendafiles(agenda_files)
@classmethod
def _load_agendafiles(self, agenda_files):
# glob for files in agenda_files
resolved_files = []
for f in agenda_files:
f = glob.glob(os.path.join(
os.path.expanduser(os.path.dirname(f)),
os.path.basename(f)))
resolved_files.extend(f)
agenda_files = [os.path.realpath(f) for f in resolved_files]
# load the agenda files into buffers
for agenda_file in agenda_files:
vim.command(u_encode(u'badd %s' % agenda_file.replace(" ", "\\ ")))
# determine the buffer nr of the agenda files
agenda_nums = [get_bufnumber(fn) for fn in agenda_files]
# collect all documents of the agenda files and create the agenda
return [ORGMODE.get_document(i) for i in agenda_nums if i is not None]
@classmethod
def opendoc(cls, split=False, switch=False):
u"""
If you are in the agenda view jump to the document the item in the
current line belongs to. cls.line2doc is used for that.
:split: if True, open the document in a new split window.
:switch: if True, switch to another window and open the the document
there.
"""
row, _ = vim.current.window.cursor
try:
bufname, bufnr, destrow = cls.line2doc[row]
except:
return
# reload source file if it is not loaded
if get_bufname(bufnr) is None:
vim.command(u_encode(u'badd %s' % bufname))
bufnr = get_bufnumber(bufname)
tmp = cls.line2doc[row]
cls.line2doc[bufnr] = tmp
# delete old endry
del cls.line2doc[row]
if split:
vim.command(u_encode(u"sbuffer %s" % bufnr))
elif switch:
vim.command(u_encode(u"wincmd w"))
vim.command(u_encode(u"buffer %d" % bufnr))
else:
vim.command(u_encode(u"buffer %s" % bufnr))
vim.command(u_encode(u"normal! %dgg <CR>" % (destrow + 1)))
@classmethod
def list_next_week(cls):
agenda_documents = cls._get_agendadocuments()
if not agenda_documents:
return
cls.list_next_week_for(agenda_documents)
@classmethod
def list_next_week_for_buffer(cls):
agenda_documents = vim.current.buffer.name
loaded_agendafiles = cls._load_agendafiles([agenda_documents])
cls.list_next_week_for(loaded_agendafiles)
@classmethod
def list_next_week_for(cls, agenda_documents):
raw_agenda = ORGMODE.agenda_manager.get_next_week_and_active_todo(
agenda_documents)
# if raw_agenda is empty, return directly
if not raw_agenda:
vim.command('echom "All caught-up. No agenda or active todo next week."')
return
# create buffer at bottom
cmd = [u'setlocal filetype=orgagenda', ]
cls._switch_to(u'AGENDA', cmd)
# line2doc is a dic with the mapping:
# line in agenda buffer --> source document
# It's easy to jump to the right document this way
cls.line2doc = {}
# format text for agenda
last_date = raw_agenda[0].active_date
final_agenda = [u'Week Agenda:', unicode(last_date)]
for i, h in enumerate(raw_agenda):
# insert date information for every new date (not datetime)
if unicode(h.active_date)[1:11] != unicode(last_date)[1:11]:
today = date.today()
# insert additional "TODAY" string
if h.active_date.year == today.year and \
h.active_date.month == today.month and \
h.active_date.day == today.day:
section = unicode(h.active_date) + u" TODAY"
today_row = len(final_agenda) + 1
else:
section = unicode(h.active_date)
final_agenda.append(section)
# update last_date
last_date = h.active_date
bufname = os.path.basename(vim.buffers[h.document.bufnr].name)
bufname = bufname[:-4] if bufname.endswith(u'.org') else bufname
formatted = u" %(bufname)s (%(bufnr)d) %(todo)s %(title)s" % {
'bufname': bufname,
'bufnr': h.document.bufnr,
'todo': h.todo,
'title': h.title
}
final_agenda.append(formatted)
cls.line2doc[len(final_agenda)] = (get_bufname(h.document.bufnr), h.document.bufnr, h.start)
# show agenda
vim.current.buffer[:] = [u_encode(i) for i in final_agenda]
vim.command(u_encode(u'setlocal nomodifiable conceallevel=2 concealcursor=nc'))
# try to jump to the position of today
try:
vim.command(u_encode(u'normal! %sgg<CR>' % today_row))
except:
pass
@classmethod
def list_all_todos(cls, current_buffer=False):
u""" List all todos in one buffer.
Args:
current_buffer (bool):
False: all agenda files
True: current org_file
"""
if current_buffer:
agenda_documents = vim.current.buffer.name
loaded_agendafiles = cls._load_agendafiles([agenda_documents])
else:
loaded_agendafiles = cls._get_agendadocuments()
if not loaded_agendafiles:
return
raw_agenda = ORGMODE.agenda_manager.get_todo(loaded_agendafiles)
cls.line2doc = {}
# create buffer at bottom
cmd = [u'setlocal filetype=orgagenda']
cls._switch_to(u'AGENDA', cmd)
# format text of agenda
final_agenda = []
for i, h in enumerate(raw_agenda):
tmp = u"%s %s" % (h.todo, h.title)
final_agenda.append(tmp)
cls.line2doc[len(final_agenda)] = (get_bufname(h.document.bufnr), h.document.bufnr, h.start)
# show agenda
vim.current.buffer[:] = [u_encode(i) for i in final_agenda]
vim.command(u_encode(u'setlocal nomodifiable conceallevel=2 concealcursor=nc'))
@classmethod
def list_timeline(cls):
"""
List a timeline of the current buffer to get an overview of the
current file.
"""
raw_agenda = ORGMODE.agenda_manager.get_timestamped_items(
[ORGMODE.get_document()])
# create buffer at bottom
cmd = [u'setlocal filetype=orgagenda']
cls._switch_to(u'AGENDA', cmd)
cls.line2doc = {}
# format text of agenda
final_agenda = []
for i, h in enumerate(raw_agenda):
tmp = fmt.format('{} {}', h.todo, h.title).lstrip().rstrip()
final_agenda.append(tmp)
cls.line2doc[len(final_agenda)] = (get_bufname(h.document.bufnr), h.document.bufnr, h.start)
# show agenda
vim.current.buffer[:] = [u_encode(i) for i in final_agenda]
vim.command(u_encode(u'setlocal nomodifiable conceallevel=2 concealcursor=nc'))
def register(self):
u"""
Registration of the plugin.
Key bindings and other initialization should be done here.
"""
add_cmd_mapping_menu(
self,
name=u"OrgAgendaTodo",
function=u'%s ORGMODE.plugins[u"Agenda"].list_all_todos()' % VIM_PY_CALL,
key_mapping=u'<localleader>cat',
menu_desrc=u'Agenda for all TODOs'
)
add_cmd_mapping_menu(
self,
name=u"OrgBufferAgendaTodo",
function=u'%s ORGMODE.plugins[u"Agenda"].list_all_todos(current_buffer=True)' % VIM_PY_CALL,
key_mapping=u'<localleader>caT',
menu_desrc=u'Agenda for all TODOs based on current buffer'
)
add_cmd_mapping_menu(
self,
name=u"OrgAgendaWeek",
function=u'%s ORGMODE.plugins[u"Agenda"].list_next_week()' % VIM_PY_CALL,
key_mapping=u'<localleader>caa',
menu_desrc=u'Agenda for the week'
)
add_cmd_mapping_menu(
self,
name=u"OrgBufferAgendaWeek",
function=u'%s ORGMODE.plugins[u"Agenda"].list_next_week_for_buffer()' % VIM_PY_CALL,
key_mapping=u'<localleader>caA',
menu_desrc=u'Agenda for the week based on current buffer'
)
add_cmd_mapping_menu(
self,
name=u'OrgAgendaTimeline',
function=u'%s ORGMODE.plugins[u"Agenda"].list_timeline()' % VIM_PY_CALL,
key_mapping=u'<localleader>caL',
menu_desrc=u'Timeline for this buffer'
)

View File

@@ -0,0 +1,316 @@
# -*- coding: utf-8 -*-
import re
from datetime import timedelta, date, datetime
import operator
import vim
from orgmode._vim import ORGMODE, echom, insert_at_cursor, get_user_input
from orgmode import settings
from orgmode.keybinding import Keybinding, Plug
from orgmode.menu import Submenu, ActionEntry, add_cmd_mapping_menu
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
from orgmode.py3compat.py_py3_string import *
class Date(object):
u"""
Handles all date and timestamp related tasks.
TODO: extend functionality (calendar, repetitions, ranges). See
http://orgmode.org/guide/Dates-and-Times.html#Dates-and-Times
"""
date_regex = r"\d\d\d\d-\d\d-\d\d"
datetime_regex = r"[A-Z]\w\w \d\d\d\d-\d\d-\d\d \d\d:\d\d>"
month_mapping = {
u'jan': 1, u'feb': 2, u'mar': 3, u'apr': 4, u'may': 5,
u'jun': 6, u'jul': 7, u'aug': 8, u'sep': 9, u'oct': 10, u'nov': 11,
u'dec': 12}
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'Dates and Scheduling')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
# commands for this plugin
self.commands = []
# set speeddating format that is compatible with orgmode
try:
if int(vim.eval(u_encode(u'exists(":SpeedDatingFormat")'))) == 2:
vim.command(u_encode(u':1SpeedDatingFormat %Y-%m-%d %a'))
vim.command(u_encode(u':1SpeedDatingFormat %Y-%m-%d %a %H:%M'))
else:
echom(u'Speeddating plugin not installed. Please install it.')
except:
echom(u'Speeddating plugin not installed. Please install it.')
@classmethod
def _modify_time(cls, startdate, modifier):
u"""Modify the given startdate according to modifier. Return the new
date or datetime.
See http://orgmode.org/manual/The-date_002ftime-prompt.html
"""
if modifier is None or modifier == '' or modifier == '.':
return startdate
# rm crap from modifier
modifier = modifier.strip()
ops = {'-': operator.sub, '+': operator.add}
# check real date
date_regex = r"(\d\d\d\d)-(\d\d)-(\d\d)"
match = re.search(date_regex, modifier)
if match:
year, month, day = match.groups()
newdate = date(int(year), int(month), int(day))
# check abbreviated date, separated with '-'
date_regex = u"(\\d{1,2})-(\\d+)-(\\d+)"
match = re.search(date_regex, modifier)
if match:
year, month, day = match.groups()
newdate = date(2000 + int(year), int(month), int(day))
# check abbreviated date, separated with '/'
# month/day
date_regex = u"(\\d{1,2})/(\\d{1,2})"
match = re.search(date_regex, modifier)
if match:
month, day = match.groups()
newdate = date(startdate.year, int(month), int(day))
# date should be always in the future
if newdate < startdate:
newdate = date(startdate.year + 1, int(month), int(day))
# check full date, separated with 'space'
# month day year
# 'sep 12 9' --> 2009 9 12
date_regex = u"(\\w\\w\\w) (\\d{1,2}) (\\d{1,2})"
match = re.search(date_regex, modifier)
if match:
gr = match.groups()
day = int(gr[1])
month = int(cls.month_mapping[gr[0]])
year = 2000 + int(gr[2])
newdate = date(year, int(month), int(day))
# check days as integers
date_regex = u"^(\\d{1,2})$"
match = re.search(date_regex, modifier)
if match:
newday, = match.groups()
newday = int(newday)
if newday > startdate.day:
newdate = date(startdate.year, startdate.month, newday)
else:
# TODO: DIRTY, fix this
# this does NOT cover all edge cases
newdate = startdate + timedelta(days=28)
newdate = date(newdate.year, newdate.month, newday)
# check for full days: Mon, Tue, Wed, Thu, Fri, Sat, Sun
modifier_lc = modifier.lower()
match = re.search(u'mon|tue|wed|thu|fri|sat|sun', modifier_lc)
if match:
weekday_mapping = {
u'mon': 0, u'tue': 1, u'wed': 2, u'thu': 3,
u'fri': 4, u'sat': 5, u'sun': 6}
diff = (weekday_mapping[modifier_lc] - startdate.weekday()) % 7
# use next weeks weekday if current weekday is the same as modifier
if diff == 0:
diff = 7
newdate = startdate + timedelta(days=diff)
# check for days modifier with appended d
match = re.search(u'^(\\+|-)(\\d*)d', modifier)
if match:
op, days = match.groups()
newdate = ops[op](startdate, timedelta(days=int(days)))
# check for days modifier without appended d
match = re.search(u'^(\\+|-)(\\d*) |^(\\+|-)(\\d*)$', modifier)
if match:
groups = match.groups()
try:
op = groups[0]
days = int(groups[1])
except:
op = groups[2]
days = int(groups[3])
newdate = ops[op](startdate, timedelta(days=days))
# check for week modifier
match = re.search(u'^(\\+|-)(\\d+)w', modifier)
if match:
op, weeks = match.groups()
newdate = ops[op](startdate, timedelta(weeks=int(weeks)))
# check for month modifier
match = re.search(u'^(\\+|-)(\\d+)m', modifier)
if match:
op, months = match.groups()
newdate = date(startdate.year, ops[op](startdate.month, int(months)),
startdate.day)
# check for year modifier
match = re.search(u'^(\\+|-)(\\d*)y', modifier)
if match:
op, years = match.groups()
newdate = date(ops[op](startdate.year, int(years)), startdate.month,
startdate.day)
# check for month day
match = re.search(
u'(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) (\\d{1,2})',
modifier.lower())
if match:
month = cls.month_mapping[match.groups()[0]]
day = int(match.groups()[1])
newdate = date(startdate.year, int(month), int(day))
# date should be always in the future
if newdate < startdate:
newdate = date(startdate.year + 1, int(month), int(day))
# check abbreviated date, separated with '/'
# month/day/year
date_regex = u"(\\d{1,2})/(\\d+)/(\\d+)"
match = re.search(date_regex, modifier)
if match:
month, day, year = match.groups()
newdate = date(2000 + int(year), int(month), int(day))
# check for month day year
# sep 12 2011
match = re.search(
u'(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) (\\d{1,2}) (\\d{1,4})',
modifier.lower())
if match:
month = int(cls.month_mapping[match.groups()[0]])
day = int(match.groups()[1])
if len(match.groups()[2]) < 4:
year = 2000 + int(match.groups()[2])
else:
year = int(match.groups()[2])
newdate = date(year, month, day)
# check for time: HH:MM
# '12:45' --> datetime(2006, 06, 13, 12, 45))
match = re.search(u'(\\d{1,2}):(\\d\\d)$', modifier)
if match:
try:
startdate = newdate
except:
pass
return datetime(
startdate.year, startdate.month, startdate.day,
int(match.groups()[0]), int(match.groups()[1]))
try:
return newdate
except:
return startdate
@classmethod
def insert_timestamp(cls, active=True):
u"""
Insert a timestamp at the cursor position.
TODO: show fancy calendar to pick the date from.
TODO: add all modifier of orgmode.
"""
today = date.today()
msg = u''.join([
u'Inserting ',
unicode(u_decode(today.strftime(u'%Y-%m-%d %a'))),
u' | Modify date'])
modifier = get_user_input(msg)
# abort if the user canceled the input prompt
if modifier is None:
return
newdate = cls._modify_time(today, modifier)
# format
if isinstance(newdate, datetime):
newdate = newdate.strftime(
u_decode(u_encode(u'%Y-%m-%d %a %H:%M')))
else:
newdate = newdate.strftime(
u_decode(u_encode(u'%Y-%m-%d %a')))
timestamp = u'<%s>' % newdate if active else u'[%s]' % newdate
insert_at_cursor(timestamp)
@classmethod
def insert_timestamp_with_calendar(cls, active=True):
u"""
Insert a timestamp at the cursor position.
Show fancy calendar to pick the date from.
TODO: add all modifier of orgmode.
"""
if int(vim.eval(u_encode(u'exists(":CalendarH")'))) != 2:
vim.command("echo 'Please install plugin Calendar to enable this function'")
return
vim.command("CalendarH")
# backup calendar_action
calendar_action = vim.eval("g:calendar_action")
vim.command("let g:org_calendar_action_backup = '" + calendar_action + "'")
vim.command("let g:calendar_action = 'CalendarAction'")
timestamp_template = u'<%s>' if active else u'[%s]'
# timestamp template
vim.command("let g:org_timestamp_template = '" + timestamp_template + "'")
def register(self):
u"""
Registration of the plugin.
Key bindings and other initialization should be done here.
"""
add_cmd_mapping_menu(
self,
name=u'OrgDateInsertTimestampActiveCmdLine',
key_mapping=u'<localleader>sa',
function=u'%s ORGMODE.plugins[u"Date"].insert_timestamp()' % VIM_PY_CALL,
menu_desrc=u'Timest&amp'
)
add_cmd_mapping_menu(
self,
name=u'OrgDateInsertTimestampInactiveCmdLine',
key_mapping='<localleader>si',
function=u'%s ORGMODE.plugins[u"Date"].insert_timestamp(False)' % VIM_PY_CALL,
menu_desrc=u'Timestamp (&inactive)'
)
add_cmd_mapping_menu(
self,
name=u'OrgDateInsertTimestampActiveWithCalendar',
key_mapping=u'<localleader>pa',
function=u'%s ORGMODE.plugins[u"Date"].insert_timestamp_with_calendar()' % VIM_PY_CALL,
menu_desrc=u'Timestamp with Calendar'
)
add_cmd_mapping_menu(
self,
name=u'OrgDateInsertTimestampInactiveWithCalendar',
key_mapping=u'<localleader>pi',
function=u'%s ORGMODE.plugins[u"Date"].insert_timestamp_with_calendar(False)' % VIM_PY_CALL,
menu_desrc=u'Timestamp with Calendar(inactive)'
)
submenu = self.menu + Submenu(u'Change &Date')
submenu + ActionEntry(u'Day &Earlier', u'<C-x>', u'<C-x>')
submenu + ActionEntry(u'Day &Later', u'<C-a>', u'<C-a>')

View File

@@ -0,0 +1,328 @@
# -*- coding: utf-8 -*-
import vim
from orgmode._vim import echo, echom, echoe, ORGMODE, apply_count, repeat, insert_at_cursor, indent_orgmode
from orgmode import settings
from orgmode.menu import Submenu, Separator, ActionEntry, add_cmd_mapping_menu
from orgmode.keybinding import Keybinding, Plug, Command
from orgmode.liborgmode.checkboxes import Checkbox
from orgmode.liborgmode.dom_obj import OrderListType
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.py_py3_string import *
from orgmode.py3compat.unicode_compatibility import *
class EditCheckbox(object):
u"""
Checkbox plugin.
"""
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'Edit Checkbox')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
# commands for this plugin
self.commands = []
@classmethod
def new_checkbox(cls, below=None, plain=None):
'''
if below is:
True -> create new list below current line
False/None -> create new list above current line
if plain is:
True -> create a plainlist item
False/None -> create an empty checkbox
'''
d = ORGMODE.get_document()
h = d.current_heading()
if h is None:
return
# init checkboxes for current heading
h.init_checkboxes()
c = h.current_checkbox()
nc = Checkbox()
nc._heading = h
# default checkbox level
level = h.level + 1
start = vim.current.window.cursor[0] - 1
# if no checkbox is found, insert at current line with indent level=1
if c is None:
h.checkboxes.append(nc)
else:
l = c.get_parent_list()
idx = c.get_index_in_parent_list()
if l is not None and idx is not None:
l.insert(idx + (1 if below else 0), nc)
# workaround for broken associations, Issue #165
nc._parent = c.parent
if below:
if c.next_sibling:
c.next_sibling._previous_sibling = nc
nc._next_sibling = c.next_sibling
c._next_sibling = nc
nc._previous_sibling = c
else:
if c.previous_sibling:
c.previous_sibling._next_sibling = nc
nc._next_sibling = c
nc._previous_sibling = c.previous_sibling
c._previous_sibling = nc
t = c.type
# increase key for ordered lists
if t[-1] in OrderListType:
try:
num = int(t[:-1]) + (1 if below else -1)
if num < 0:
# don't decrease to numbers below zero
echom(u"Can't decrement further than '0'")
return
t = '%d%s' % (num, t[-1])
except ValueError:
try:
char = ord(t[:-1]) + (1 if below else -1)
if below:
if char == 91:
# stop incrementing at Z (90)
echom(u"Can't increment further than 'Z'")
return
elif char == 123:
# increment from z (122) to A
char = 65
else:
if char == 96:
# stop decrementing at a (97)
echom(u"Can't decrement further than 'a'")
return
elif char == 64:
# decrement from A (65) to z
char = 122
t = u'%s%s' % (chr(char), t[-1])
except ValueError:
pass
nc.type = t
level = c.level
if below:
start = c.end_of_last_child
else:
start = c.start
if plain: # only create plainlist item when requested
nc.status = None
nc.level = level
if below:
start += 1
# vim's buffer behave just opposite to Python's list when inserting a
# new item. The new entry is appended in vim put prepended in Python!
vim.current.buffer.append("") # workaround for neovim
vim.current.buffer[start:start] = [unicode(nc)]
del vim.current.buffer[-1] # restore from workaround for neovim
# update checkboxes status
cls.update_checkboxes_status()
# do not start insert upon adding new checkbox, Issue #211
if int(settings.get(u'org_prefer_insert_mode', u'1')):
vim.command(u_encode(u'exe "normal %dgg"|startinsert!' % (start + 1, )))
else:
vim.command(u_encode(u'exe "normal %dgg$"' % (start + 1, )))
@classmethod
def toggle(cls, checkbox=None):
u"""
Toggle the checkbox given in the parameter.
If the checkbox is not given, it will toggle the current checkbox.
"""
d = ORGMODE.get_document()
current_heading = d.current_heading()
# init checkboxes for current heading
if current_heading is None:
return
current_heading = current_heading.init_checkboxes()
if checkbox is None:
# get current_checkbox
c = current_heading.current_checkbox()
# no checkbox found
if c is None:
cls.update_checkboxes_status()
return
else:
c = checkbox
if c.status == Checkbox.STATUS_OFF or c.status is None:
# set checkbox status on if all children are on
if c.all_children_status()[0] == 0 or c.are_children_all(Checkbox.STATUS_ON):
c.toggle()
d.write_checkbox(c)
elif c.status is None:
c.status = Checkbox.STATUS_OFF
d.write_checkbox(c)
elif c.status == Checkbox.STATUS_ON:
if c.all_children_status()[0] == 0 or c.is_child_one(Checkbox.STATUS_OFF):
c.toggle()
d.write_checkbox(c)
elif c.status == Checkbox.STATUS_INT:
# can't toggle intermediate state directly according to emacs orgmode
pass
# update checkboxes status
cls.update_checkboxes_status()
@classmethod
def _update_subtasks(cls):
d = ORGMODE.get_document()
h = d.current_heading()
# init checkboxes for current heading
h.init_checkboxes()
# update heading subtask info
c = h.first_checkbox
if c is None:
return
total, on = c.all_siblings_status()
h.update_subtasks(total, on)
# update all checkboxes under current heading
cls._update_checkboxes_subtasks(c)
@classmethod
def _update_checkboxes_subtasks(cls, checkbox):
# update checkboxes
for c in checkbox.all_siblings():
if c.children:
total, on = c.first_child.all_siblings_status()
c.update_subtasks(total, on)
cls._update_checkboxes_subtasks(c.first_child)
@classmethod
def update_checkboxes_status(cls):
d = ORGMODE.get_document()
h = d.current_heading()
if h is None:
return
# init checkboxes for current heading
h.init_checkboxes()
cls._update_checkboxes_status(h.first_checkbox)
cls._update_subtasks()
@classmethod
def _update_checkboxes_status(cls, checkbox=None):
u""" helper function for update checkboxes status
:checkbox: The first checkbox of this indent level
:return: The status of the parent checkbox
"""
if checkbox is None:
return
status_off, status_on, status_int, total = 0, 0, 0, 0
# update all top level checkboxes' status
for c in checkbox.all_siblings():
current_status = c.status
# if this checkbox is not leaf, its status should determine by all its children
if c.all_children_status()[0] > 0:
current_status = cls._update_checkboxes_status(c.first_child)
# don't update status if the checkbox has no status
if c.status is None:
current_status = None
# the checkbox needs to have status
else:
total += 1
# count number of status in this checkbox level
if current_status == Checkbox.STATUS_OFF:
status_off += 1
elif current_status == Checkbox.STATUS_ON:
status_on += 1
elif current_status == Checkbox.STATUS_INT:
status_int += 1
# write status if any update
if current_status is not None and c.status != current_status:
c.status = current_status
d = ORGMODE.get_document()
d.write_checkbox(c)
parent_status = Checkbox.STATUS_INT
# all silbing checkboxes are off status
if total == 0:
pass
elif status_off == total:
parent_status = Checkbox.STATUS_OFF
# all silbing checkboxes are on status
elif status_on == total:
parent_status = Checkbox.STATUS_ON
# one silbing checkbox is on or int status
elif status_on != 0 or status_int != 0:
parent_status = Checkbox.STATUS_INT
# other cases
else:
parent_status = None
return parent_status
def register(self):
u"""
Registration of the plugin.
Key bindings and other initialization should be done here.
"""
# default setting if it is not already set.
# checkbox related operation
add_cmd_mapping_menu(
self,
name=u'OrgCheckBoxNewAbove',
function=u'%s ORGMODE.plugins[u"EditCheckbox"].new_checkbox()<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>cN',
menu_desrc=u'New CheckBox Above'
)
add_cmd_mapping_menu(
self,
name=u'OrgCheckBoxNewBelow',
function=u'%s ORGMODE.plugins[u"EditCheckbox"].new_checkbox(below=True)<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>cn',
menu_desrc=u'New CheckBox Below'
)
add_cmd_mapping_menu(
self,
name=u'OrgCheckBoxToggle',
function=u':silent! %s ORGMODE.plugins[u"EditCheckbox"].toggle()<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>cc',
menu_desrc=u'Toggle Checkbox'
)
add_cmd_mapping_menu(
self,
name=u'OrgCheckBoxUpdate',
function=u':silent! %s ORGMODE.plugins[u"EditCheckbox"].update_checkboxes_status()<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>c#',
menu_desrc=u'Update Subtasks'
)
# plainlist related operation
add_cmd_mapping_menu(
self,
name=u'OrgPlainListItemNewAbove',
function=u'%s ORGMODE.plugins[u"EditCheckbox"].new_checkbox(plain=True)<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>cL',
menu_desrc=u'New PlainList Item Above'
)
add_cmd_mapping_menu(
self,
name=u'OrgPlainListItemNewBelow',
function=u'%s ORGMODE.plugins[u"EditCheckbox"].new_checkbox(below=True, plain=True)<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>cl',
menu_desrc=u'New PlainList Item Below'
)

View File

@@ -0,0 +1,428 @@
# -*- coding: utf-8 -*-
import vim
from orgmode._vim import ORGMODE, apply_count, repeat, realign_tags
from orgmode import settings
from orgmode.exceptions import HeadingDomError
from orgmode.keybinding import Keybinding, Plug, MODE_INSERT, MODE_NORMAL
from orgmode.menu import Submenu, Separator, ActionEntry
from orgmode.liborgmode.base import Direction
from orgmode.liborgmode.headings import Heading
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.py_py3_string import *
class EditStructure(object):
u""" EditStructure plugin """
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'&Edit Structure')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
@classmethod
def new_heading(cls, below=None, insert_mode=False, end_of_last_child=False):
u"""
:below: True, insert heading below current heading, False,
insert heading above current heading, None, special
behavior for insert mode, use the current text as
heading
:insert_mode: True, if action is performed in insert mode
:end_of_last_child: True, insert heading at the end of last child,
otherwise the newly created heading will "take
over" the current heading's children
"""
d = ORGMODE.get_document()
current_heading = d.current_heading()
cursor = vim.current.window.cursor[:]
if not current_heading:
# the user is in meta data region
pos = cursor[0] - 1
heading = Heading(title=d.meta_information[pos], body=d.meta_information[pos + 1:])
d.headings.insert(0, heading)
del d.meta_information[pos:]
d.write()
vim.command(u_encode(u'exe "normal %dgg"|startinsert!' % (heading.start_vim, )))
return heading
# check for plain list(checkbox)
current_heading.init_checkboxes()
c = current_heading.current_checkbox()
if c is not None:
ORGMODE.plugins[u"EditCheckbox"].new_checkbox(below, not c.status)
return
heading = Heading(level=current_heading.level)
# it's weird but this is the behavior of original orgmode
if below is None:
below = cursor[1] != 0 or end_of_last_child
# insert newly created heading
l = current_heading.get_parent_list()
idx = current_heading.get_index_in_parent_list()
if l is not None and idx is not None:
l.insert(idx + (1 if below else 0), heading)
else:
raise HeadingDomError(u'Current heading is not properly linked in DOM')
if below and not end_of_last_child:
# append heading at the end of current heading and also take
# over the children of current heading
for child in current_heading.children:
heading.children.append(child, taint=False)
current_heading.children.remove_slice(
0, len(current_heading.children),
taint=False)
# if cursor is currently on a heading, insert parts of it into the
# newly created heading
if insert_mode and cursor[1] != 0 and cursor[0] == current_heading.start_vim:
offset = cursor[1] - current_heading.level - 1 - (
len(current_heading.todo) + 1 if current_heading.todo else 0)
if offset < 0:
offset = 0
if int(settings.get(u'org_improve_split_heading', u'1')) and \
offset > 0 and len(current_heading.title) == offset + 1 \
and current_heading.title[offset - 1] not in (u' ', u'\t'):
offset += 1
heading.title = current_heading.title[offset:]
current_heading.title = current_heading.title[:offset]
heading.body = current_heading.body[:]
current_heading.body = []
d.write()
# do not start insert upon adding new headings, unless already in insert mode. Issue #211
if int(settings.get(u'org_prefer_insert_mode', u'1')) or insert_mode:
vim.command(u_encode(u'exe "normal %dgg"|startinsert!' % (heading.start_vim, )))
else:
vim.command(u_encode(u'exe "normal %dgg$"' % (heading.start_vim, )))
# return newly created heading
return heading
@classmethod
def _append_heading(cls, heading, parent):
if heading.level <= parent.level:
raise ValueError('Heading level not is lower than parent level: %d ! > %d' % (heading.level, parent.level))
if parent.children and parent.children[-1].level < heading.level:
cls._append_heading(heading, parent.children[-1])
else:
parent.children.append(heading, taint=False)
@classmethod
def _change_heading_level(cls, level, including_children=True, on_heading=False, insert_mode=False):
u"""
Change level of heading realtively with or without including children.
:level: the number of levels to promote/demote heading
:including_children: True if should should be included in promoting/demoting
:on_heading: True if promoting/demoting should only happen when the cursor is on the heading
:insert_mode: True if vim is in insert mode
"""
# TODO : current promote and demote works for only headings. Since
# checkboxes also have tree structure. We should think of
# expanding the functionality of promoting and demoting to
# checkboxes as well
d = ORGMODE.get_document()
current_heading = d.current_heading()
if not current_heading or on_heading and current_heading.start_vim != vim.current.window.cursor[0]:
# TODO figure out the actually pressed keybinding and feed these
# keys instead of making keys up like this
if level > 0:
if insert_mode:
vim.eval(u_encode(u'feedkeys("\\<C-t>", "n")'))
elif including_children:
vim.eval(u_encode(u'feedkeys(">]]", "n")'))
elif on_heading:
vim.eval(u_encode(u'feedkeys(">>", "n")'))
else:
vim.eval(u_encode(u'feedkeys(">}", "n")'))
else:
if insert_mode:
vim.eval(u_encode(u'feedkeys("\\<C-d>", "n")'))
elif including_children:
vim.eval(u_encode(u'feedkeys("<]]", "n")'))
elif on_heading:
vim.eval(u_encode(u'feedkeys("<<", "n")'))
else:
vim.eval(u_encode(u'feedkeys("<}", "n")'))
# return True because otherwise apply_count will not work
return True
# don't allow demotion below level 1
if current_heading.level == 1 and level < 1:
return False
# reduce level of demotion to a minimum heading level of 1
if (current_heading.level + level) < 1:
level = 1
def indent(heading, ic):
if not heading:
return
heading.level += level
if ic:
for child in heading.children:
indent(child, ic)
# save cursor position
c = vim.current.window.cursor[:]
# indent the promoted/demoted heading
indent_end_vim = current_heading.end_of_last_child_vim if including_children else current_heading.end_vim
indent(current_heading, including_children)
# when changing the level of a heading, its position in the DOM
# needs to be updated. It's likely that the heading gets a new
# parent and new children when demoted or promoted
# find new parent
p = current_heading.parent
pl = current_heading.get_parent_list()
ps = current_heading.previous_sibling
nhl = current_heading.level
if level > 0:
# demotion
# subheading or top level heading
if ps and nhl > ps.level:
pl.remove(current_heading, taint=False)
# find heading that is the new parent heading
oh = ps
h = ps
while nhl > h.level:
oh = h
if h.children:
h = h.children[-1]
else:
break
np = h if nhl > h.level else oh
# append current heading to new heading
np.children.append(current_heading, taint=False)
# if children are not included, distribute them among the
# parent heading and it's siblings
if not including_children:
for h in current_heading.children[:]:
if h and h.level <= nhl:
cls._append_heading(h, np)
current_heading.children.remove(h, taint=False)
else:
# promotion
if p and nhl <= p.level:
idx = current_heading.get_index_in_parent_list() + 1
# find the new parent heading
oh = p
h = p
while nhl <= h.level:
# append new children to current heading
for child in h.children[idx:]:
cls._append_heading(child, current_heading)
h.children.remove_slice(idx, len(h.children), taint=False)
idx = h.get_index_in_parent_list() + 1
if h.parent:
h = h.parent
else:
break
ns = oh.next_sibling
while ns and ns.level > current_heading.level:
nns = ns.next_sibling
cls._append_heading(ns, current_heading)
ns = nns
# append current heading to new parent heading / document
pl.remove(current_heading, taint=False)
if nhl > h.level:
h.children.insert(idx, current_heading, taint=False)
else:
d.headings.insert(idx, current_heading, taint=False)
d.write()
# restore cursor position
vim.current.window.cursor = (c[0], c[1] + level)
return True
@classmethod
@realign_tags
@repeat
@apply_count
def demote_heading(cls, including_children=True, on_heading=False, insert_mode=False):
if cls._change_heading_level(1, including_children=including_children, on_heading=on_heading, insert_mode=insert_mode):
if including_children:
return u'OrgDemoteSubtree'
return u'OrgDemoteHeading'
@classmethod
@realign_tags
@repeat
@apply_count
def promote_heading(cls, including_children=True, on_heading=False, insert_mode=False):
if cls._change_heading_level(-1, including_children=including_children, on_heading=on_heading, insert_mode=insert_mode):
if including_children:
return u'OrgPromoteSubtreeNormal'
return u'OrgPromoteHeadingNormal'
@classmethod
def _move_heading(cls, direction=Direction.FORWARD, including_children=True):
u""" Move heading up or down
:returns: heading or None
"""
d = ORGMODE.get_document()
current_heading = d.current_heading()
if not current_heading or \
(direction == Direction.FORWARD and not current_heading.next_sibling) or \
(direction == Direction.BACKWARD and not current_heading.previous_sibling):
return None
cursor_offset = vim.current.window.cursor[0] - (current_heading._orig_start + 1)
l = current_heading.get_parent_list()
if l is None:
raise HeadingDomError(u'Current heading is not properly linked in DOM')
if not including_children:
if current_heading.previous_sibling:
npl = current_heading.previous_sibling.children
for child in current_heading.children:
npl.append(child, taint=False)
elif current_heading.parent:
# if the current heading doesn't have a previous sibling it
# must be the first heading
np = current_heading.parent
for child in current_heading.children:
cls._append_heading(child, np)
else:
# if the current heading doesn't have a parent, its children
# must be added as top level headings to the document
npl = l
for child in current_heading.children[::-1]:
npl.insert(0, child, taint=False)
current_heading.children.remove_slice(0, len(current_heading.children), taint=False)
idx = current_heading.get_index_in_parent_list()
if idx is None:
raise HeadingDomError(u'Current heading is not properly linked in DOM')
offset = 1 if direction == Direction.FORWARD else -1
del l[idx]
l.insert(idx + offset, current_heading)
d.write()
vim.current.window.cursor = (
current_heading.start_vim + cursor_offset,
vim.current.window.cursor[1])
return True
@classmethod
@repeat
@apply_count
def move_heading_upward(cls, including_children=True):
if cls._move_heading(direction=Direction.BACKWARD, including_children=including_children):
if including_children:
return u'OrgMoveSubtreeUpward'
return u'OrgMoveHeadingUpward'
@classmethod
@repeat
@apply_count
def move_heading_downward(cls, including_children=True):
if cls._move_heading(direction=Direction.FORWARD, including_children=including_children):
if including_children:
return u'OrgMoveSubtreeDownward'
return u'OrgMoveHeadingDownward'
def register(self):
u"""
Registration of plugin. Key bindings and other initialization should be done.
"""
# EditStructure related default settings
settings.set(u'org_improve_split_heading', u'1')
# EditStructure related keybindings
self.keybindings.append(Keybinding(u'<C-S-CR>',
Plug(u'OrgNewHeadingAboveNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].new_heading(below=False)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'New Heading &above', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<localleader>hN', u'<Plug>OrgNewHeadingAboveNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<localleader><CR>', u'<Plug>OrgNewHeadingAboveNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<S-CR>',
Plug(u'OrgNewHeadingBelowNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].new_heading(below=True)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'New Heading &below', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<localleader>hh', u'<Plug>OrgNewHeadingBelowNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<localleader><CR>', u'<Plug>OrgNewHeadingBelowNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<C-CR>', Plug(u'OrgNewHeadingBelowAfterChildrenNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].new_heading(below=True, end_of_last_child=True)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'New Heading below, after &children', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<localleader>hn', u'<Plug>OrgNewHeadingBelowAfterChildrenNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<CR>', u'<Plug>OrgNewHeadingBelowAfterChildrenNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<C-S-CR>', Plug(u'OrgNewHeadingAboveInsert', u'<C-o>:<C-u>silent! %s ORGMODE.plugins[u"EditStructure"].new_heading(below=False, insert_mode=True)<CR>' % VIM_PY_CALL, mode=MODE_INSERT)))
self.keybindings.append(Keybinding(u'<S-CR>', Plug(u'OrgNewHeadingBelowInsert', u'<C-o>:<C-u>silent! %s ORGMODE.plugins[u"EditStructure"].new_heading(below=True, insert_mode=True)<CR>' % VIM_PY_CALL, mode=MODE_INSERT)))
self.keybindings.append(Keybinding(u'<C-CR>', Plug(u'OrgNewHeadingBelowAfterChildrenInsert', u'<C-o>:<C-u>silent! %s ORGMODE.plugins[u"EditStructure"].new_heading(insert_mode=True, end_of_last_child=True)<CR>' % VIM_PY_CALL, mode=MODE_INSERT)))
self.menu + Separator()
self.keybindings.append(Keybinding(u'm{', Plug(u'OrgMoveHeadingUpward',
u'%s ORGMODE.plugins[u"EditStructure"].move_heading_upward(including_children=False)<CR>' % VIM_PY_CALL)))
self.keybindings.append(Keybinding(u'm[[',
Plug(u'OrgMoveSubtreeUpward', u'%s ORGMODE.plugins[u"EditStructure"].move_heading_upward()<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'Move Subtree &Up', self.keybindings[-1])
self.keybindings.append(Keybinding(u'm}',
Plug(u'OrgMoveHeadingDownward', u'%s ORGMODE.plugins[u"EditStructure"].move_heading_downward(including_children=False)<CR>' % VIM_PY_CALL)))
self.keybindings.append(Keybinding(u'm]]',
Plug(u'OrgMoveSubtreeDownward', u'%s ORGMODE.plugins[u"EditStructure"].move_heading_downward()<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'Move Subtree &Down', self.keybindings[-1])
self.menu + Separator()
self.menu + ActionEntry(u'&Copy Heading', u'yah', u'yah')
self.menu + ActionEntry(u'C&ut Heading', u'dah', u'dah')
self.menu + Separator()
self.menu + ActionEntry(u'&Copy Subtree', u'yar', u'yar')
self.menu + ActionEntry(u'C&ut Subtree', u'dar', u'dar')
self.menu + ActionEntry(u'&Paste Subtree', u'p', u'p')
self.menu + Separator()
self.keybindings.append(Keybinding(u'<ah', Plug(u'OrgPromoteHeadingNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].promote_heading(including_children=False)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Promote Heading', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<<', Plug(u'OrgPromoteOnHeadingNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].promote_heading(including_children=False, on_heading=True)<CR>' % VIM_PY_CALL)))
self.keybindings.append(Keybinding(u'<{', u'<Plug>OrgPromoteHeadingNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<ih', u'<Plug>OrgPromoteHeadingNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<ar', Plug(u'OrgPromoteSubtreeNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].promote_heading()<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Promote Subtree', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<[[', u'<Plug>OrgPromoteSubtreeNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'<ir', u'<Plug>OrgPromoteSubtreeNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'>ah', Plug(u'OrgDemoteHeadingNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].demote_heading(including_children=False)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Demote Heading', self.keybindings[-1])
self.keybindings.append(Keybinding(u'>>', Plug(u'OrgDemoteOnHeadingNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].demote_heading(including_children=False, on_heading=True)<CR>' % VIM_PY_CALL)))
self.keybindings.append(Keybinding(u'>}', u'<Plug>OrgDemoteHeadingNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'>ih', u'<Plug>OrgDemoteHeadingNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'>ar', Plug(u'OrgDemoteSubtreeNormal', u':silent! %s ORGMODE.plugins[u"EditStructure"].demote_heading()<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Demote Subtree', self.keybindings[-1])
self.keybindings.append(Keybinding(u'>]]', u'<Plug>OrgDemoteSubtreeNormal', mode=MODE_NORMAL))
self.keybindings.append(Keybinding(u'>ir', u'<Plug>OrgDemoteSubtreeNormal', mode=MODE_NORMAL))
# other keybindings
self.keybindings.append(Keybinding(u'<C-d>', Plug(u'OrgPromoteOnHeadingInsert', u'<C-o>:silent! %s ORGMODE.plugins[u"EditStructure"].promote_heading(including_children=False, on_heading=True, insert_mode=True)<CR>' % VIM_PY_CALL, mode=MODE_INSERT)))
self.keybindings.append(Keybinding(u'<C-t>', Plug(u'OrgDemoteOnHeadingInsert', u'<C-o>:silent! %s ORGMODE.plugins[u"EditStructure"].demote_heading(including_children=False, on_heading=True, insert_mode=True)<CR>' % VIM_PY_CALL, mode=MODE_INSERT)))

View File

@@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
import os
import subprocess
import vim
from orgmode._vim import ORGMODE, echoe, echom
from orgmode.menu import Submenu, ActionEntry, add_cmd_mapping_menu
from orgmode.keybinding import Keybinding, Plug, Command
from orgmode import settings
from orgmode.py3compat.py_py3_string import *
class Export(object):
u"""
Export a orgmode file using emacs orgmode.
This is a *very simple* wrapper of the emacs/orgmode export. emacs and
orgmode need to be installed. We simply call emacs with some options to
export the .org.
TODO: Offer export options in vim. Don't use the menu.
TODO: Maybe use a native implementation.
"""
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'Export')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
# commands for this plugin
self.commands = []
@classmethod
def _get_init_script(cls):
init_script = settings.get(u'org_export_init_script', u'')
if init_script:
init_script = os.path.expandvars(os.path.expanduser(init_script))
if os.path.exists(init_script):
return init_script
else:
echoe(u'Unable to find init script %s' % init_script)
@classmethod
def _export(cls, format_):
"""Export current file to format.
Args:
format_: pdf or html
Returns:
return code
"""
emacsbin = os.path.expandvars(os.path.expanduser(
settings.get(u'org_export_emacs', u'/usr/bin/emacs')))
if not os.path.exists(emacsbin):
echoe(u'Unable to find emacs binary %s' % emacsbin)
# build the export command
cmd = [
emacsbin,
u'-nw',
u'--batch',
u'--visit=%s' % vim.eval(u'expand("%:p")'),
u'--funcall=%s' % format_
]
# source init script as well
init_script = cls._get_init_script()
if init_script:
cmd.extend(['--script', init_script])
# export
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
if p.returncode != 0 or settings.get(u'org_export_verbose') == 1:
echom('\n'.join(map(lambda x: x.decode(), p.communicate())))
return p.returncode
@classmethod
def topdf(cls):
u"""Export the current buffer as pdf using emacs orgmode."""
ret = cls._export(u'org-latex-export-to-pdf')
if ret != 0:
echoe(u'PDF export failed.')
else:
echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'pdf'))
@classmethod
def tobeamer(cls):
u"""Export the current buffer as beamer pdf using emacs orgmode."""
ret = cls._export(u'org-beamer-export-to-pdf')
if ret != 0:
echoe(u'PDF export failed.')
else:
echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'pdf'))
@classmethod
def tohtml(cls):
u"""Export the current buffer as html using emacs orgmode."""
ret = cls._export(u'org-html-export-to-html')
if ret != 0:
echoe(u'HTML export failed.')
else:
echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'html'))
@classmethod
def tolatex(cls):
u"""Export the current buffer as latex using emacs orgmode."""
ret = cls._export(u'org-latex-export-to-latex')
if ret != 0:
echoe(u'latex export failed.')
else:
echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'tex'))
@classmethod
def tomarkdown(cls):
u"""Export the current buffer as markdown using emacs orgmode."""
ret = cls._export(u'org-md-export-to-markdown')
if ret != 0:
echoe('Markdown export failed. Make sure org-md-export-to-markdown is loaded in emacs, see the manual for details.')
else:
echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'md'))
def register(self):
u"""Registration and keybindings."""
# path to emacs executable
settings.set(u'org_export_emacs', u'/usr/bin/emacs')
# verbose output for export
settings.set(u'org_export_verbose', 0)
# allow the user to define an initialization script
settings.set(u'org_export_init_script', u'')
# to PDF
add_cmd_mapping_menu(
self,
name=u'OrgExportToPDF',
function=u':%s ORGMODE.plugins[u"Export"].topdf()<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>ep',
menu_desrc=u'To PDF (via Emacs)'
)
# to Beamer PDF
add_cmd_mapping_menu(
self,
name=u'OrgExportToBeamerPDF',
function=u':%s ORGMODE.plugins[u"Export"].tobeamer()<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>eb',
menu_desrc=u'To Beamer PDF (via Emacs)'
)
# to latex
add_cmd_mapping_menu(
self,
name=u'OrgExportToLaTeX',
function=u':%s ORGMODE.plugins[u"Export"].tolatex()<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>el',
menu_desrc=u'To LaTeX (via Emacs)'
)
# to HTML
add_cmd_mapping_menu(
self,
name=u'OrgExportToHTML',
function=u':%s ORGMODE.plugins[u"Export"].tohtml()<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>eh',
menu_desrc=u'To HTML (via Emacs)'
)
# to Markdown
add_cmd_mapping_menu(
self,
name=u'OrgExportToMarkdown',
function=u':%s ORGMODE.plugins[u"Export"].tomarkdown()<CR>' % VIM_PY_CALL,
key_mapping=u'<localleader>em',
menu_desrc=u'To Markdown (via Emacs)'
)

View File

@@ -0,0 +1,219 @@
# -*- coding: utf-8 -*-
import re
import vim
from orgmode._vim import echom, ORGMODE, realign_tags
from orgmode.menu import Submenu, Separator, ActionEntry
from orgmode.keybinding import Keybinding, Plug, Command
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.py_py3_string import *
class Hyperlinks(object):
u""" Hyperlinks plugin """
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'Hyperlinks')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
# commands for this plugin
self.commands = []
uri_match = re.compile(
r'^\[{2}(?P<uri>[^][]*)(\]\[(?P<description>[^][]*))?\]{2}')
@classmethod
def _get_link(cls, cursor=None):
u"""
Get the link the cursor is on and return it's URI and description
:cursor: None or (Line, Column)
:returns: None if no link was found, otherwise {uri:URI,
description:DESCRIPTION, line:LINE, start:START, end:END}
or uri and description could be None if not set
"""
cursor = cursor if cursor else vim.current.window.cursor
line = u_decode(vim.current.buffer[cursor[0] - 1])
# if the cursor is on the last bracket, it's not recognized as a hyperlink
start = line.rfind(u'[[', 0, cursor[1])
if start == -1:
start = line.rfind(u'[[', 0, cursor[1] + 2)
end = line.find(u']]', cursor[1])
if end == -1:
end = line.find(u']]', cursor[1] - 1)
# extract link
if start != -1 and end != -1:
end += 2
match = Hyperlinks.uri_match.match(line[start:end])
res = {
u'line': line,
u'start': start,
u'end': end,
u'uri': None,
u'description': None}
if match:
res.update(match.groupdict())
# reverse character escaping(partly done due to matching)
res[u'uri'] = res[u'uri'].replace(u'\\\\', u'\\')
return res
@classmethod
def follow(cls, action=u'openLink', visual=u''):
u""" Follow hyperlink. If called on a regular string UTL determines the
outcome. Normally a file with that name will be opened.
:action: "copy" if the link should be copied to clipboard, otherwise
the link will be opened
:visual: "visual" if Universal Text Linking should be triggered in
visual mode
:returns: URI or None
"""
if not int(vim.eval(u'exists(":Utl")')):
echom(u'Universal Text Linking plugin not installed, unable to proceed.')
return
action = u'copyLink' \
if (action and action.startswith(u'copy')) \
else u'openLink'
visual = u'visual' if visual and visual.startswith(u'visual') else u''
link = Hyperlinks._get_link()
if link and link[u'uri'] is not None:
# call UTL with the URI
vim.command(u_encode(u'Utl %s %s %s' % (action, visual, link[u'uri'])))
return link[u'uri']
else:
# call UTL and let it decide what to do
vim.command(u_encode(u'Utl %s %s' % (action, visual)))
@classmethod
@realign_tags
def insert(cls, uri=None, description=None):
u""" Inserts a hyperlink. If no arguments are provided, an interactive
query will be started.
:uri: The URI that will be opened
:description: An optional description that will be displayed instead of
the URI
:returns: (URI, description)
"""
link = Hyperlinks._get_link()
if link:
if uri is None and link[u'uri'] is not None:
uri = link[u'uri']
if description is None and link[u'description'] is not None:
description = link[u'description']
if uri is None:
uri = vim.eval(u'input("Link: ", "", "file")')
elif link:
uri = vim.eval(u'input("Link: ", "%s", "file")' % link[u'uri'])
if uri is None:
return
else:
uri = u_decode(uri)
# character escaping
uri = uri.replace(u'\\', u'\\\\\\\\')
uri = uri.replace(u' ', u'\\ ')
if description is None:
description = u_decode(vim.eval(u'input("Description: ")'))
elif link:
description = vim.eval(
u'input("Description: ", "%s")' %
u_decode(link[u'description']))
if description is None:
return
cursor = vim.current.window.cursor
cl = u_decode(vim.current.buffer[cursor[0] - 1])
head = cl[:cursor[1] + 1] if not link else cl[:link[u'start']]
tail = cl[cursor[1] + 1:] if not link else cl[link[u'end']:]
separator = u''
if description:
separator = u']['
if uri or description:
vim.current.buffer[cursor[0] - 1] = \
u_encode(u''.join((head, u'[[%s%s%s]]' % (uri, separator, description), tail)))
elif link:
vim.current.buffer[cursor[0] - 1] = \
u_encode(u''.join((head, tail)))
def register(self):
u"""
Registration of plugin. Key bindings and other initialization should be done.
"""
cmd = Command(
u'OrgHyperlinkFollow',
u'%s ORGMODE.plugins[u"Hyperlinks"].follow()' % VIM_PY_CALL)
self.commands.append(cmd)
self.keybindings.append(
Keybinding(u'gl', Plug(u'OrgHyperlinkFollow', self.commands[-1])))
self.menu + ActionEntry(u'&Follow Link', self.keybindings[-1])
cmd = Command(
u'OrgHyperlinkCopy',
u'%s ORGMODE.plugins[u"Hyperlinks"].follow(action=u"copy")' % VIM_PY_CALL)
self.commands.append(cmd)
self.keybindings.append(
Keybinding(u'gyl', Plug(u'OrgHyperlinkCopy', self.commands[-1])))
self.menu + ActionEntry(u'&Copy Link', self.keybindings[-1])
cmd = Command(
u'OrgHyperlinkInsert',
u'%s ORGMODE.plugins[u"Hyperlinks"].insert(<f-args>)' % VIM_PY_CALL,
arguments=u'*')
self.commands.append(cmd)
self.keybindings.append(
Keybinding(u'gil', Plug(u'OrgHyperlinkInsert', self.commands[-1])))
self.menu + ActionEntry(u'&Insert Link', self.keybindings[-1])
self.menu + Separator()
# find next link
cmd = Command(
u'OrgHyperlinkNextLink',
u":if search('\\[\\{2}\\zs[^][]*\\(\\]\\[[^][]*\\)\\?\\ze\\]\\{2}', 's') == 0 | echo 'No further link found.' | endif")
self.commands.append(cmd)
self.keybindings.append(
Keybinding(u'gn', Plug(u'OrgHyperlinkNextLink', self.commands[-1])))
self.menu + ActionEntry(u'&Next Link', self.keybindings[-1])
# find previous link
cmd = Command(
u'OrgHyperlinkPreviousLink',
u":if search('\\[\\{2}\\zs[^][]*\\(\\]\\[[^][]*\\)\\?\\ze\\]\\{2}', 'bs') == 0 | echo 'No further link found.' | endif")
self.commands.append(cmd)
self.keybindings.append(
Keybinding(u'go', Plug(u'OrgHyperlinkPreviousLink', self.commands[-1])))
self.menu + ActionEntry(u'&Previous Link', self.keybindings[-1])
self.menu + Separator()
# Descriptive Links
cmd = Command(u'OrgHyperlinkDescriptiveLinks', u':setlocal cole=2')
self.commands.append(cmd)
self.menu + ActionEntry(u'&Descriptive Links', self.commands[-1])
# Literal Links
cmd = Command(u'OrgHyperlinkLiteralLinks', u':setlocal cole=0')
self.commands.append(cmd)
self.menu + ActionEntry(u'&Literal Links', self.commands[-1])

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
import vim
from orgmode._vim import echo, echom, echoe, ORGMODE, apply_count, repeat
from orgmode.menu import Submenu, Separator, ActionEntry
from orgmode.keybinding import Keybinding, Plug, Command
from orgmode.py3compat.py_py3_string import *
class LoggingWork(object):
u""" LoggingWork plugin """
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'&Logging work')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
# commands for this plugin
self.commands = []
@classmethod
def action(cls):
u""" Some kind of action
:returns: TODO
"""
pass
def register(self):
u"""
Registration of plugin. Key bindings and other initialization should be done.
"""
# an Action menu entry which binds "keybinding" to action ":action"
self.commands.append(Command(u'OrgLoggingRecordDoneTime', u'%s ORGMODE.plugins[u"LoggingWork"].action()' % VIM_PY_CALL))
self.menu + ActionEntry(u'&Record DONE time', self.commands[-1])

View File

@@ -0,0 +1,171 @@
# -*- coding: utf-8 -*-
import vim
from orgmode._vim import ORGMODE, apply_count
from orgmode.menu import Submenu
from orgmode.keybinding import Keybinding, Plug, MODE_VISUAL, MODE_OPERATOR
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.py_py3_string import *
class Misc(object):
u""" Miscellaneous functionality """
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'Misc')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
@classmethod
def jump_to_first_character(cls):
heading = ORGMODE.get_document().current_heading()
if not heading or heading.start_vim != vim.current.window.cursor[0]:
vim.eval(u_encode(u'feedkeys("^", "n")'))
return
vim.current.window.cursor = (vim.current.window.cursor[0], heading.level + 1)
@classmethod
def edit_at_first_character(cls):
heading = ORGMODE.get_document().current_heading()
if not heading or heading.start_vim != vim.current.window.cursor[0]:
vim.eval(u_encode(u'feedkeys("I", "n")'))
return
vim.current.window.cursor = (vim.current.window.cursor[0], heading.level + 1)
vim.command(u_encode(u'startinsert'))
# @repeat
@classmethod
@apply_count
def i_heading(cls, mode=u'visual', selection=u'inner', skip_children=False):
u"""
inner heading text object
"""
heading = ORGMODE.get_document().current_heading()
if heading:
if selection != u'inner':
heading = heading if not heading.parent else heading.parent
line_start, col_start = [int(i) for i in vim.eval(u_encode(u'getpos("\'<")'))[1:3]]
line_end, col_end = [int(i) for i in vim.eval(u_encode(u'getpos("\'>")'))[1:3]]
if mode != u'visual':
line_start = vim.current.window.cursor[0]
line_end = line_start
start = line_start
end = line_end
move_one_character_back = u'' if mode == u'visual' else u'h'
if heading.start_vim < line_start:
start = heading.start_vim
if heading.end_vim > line_end and not skip_children:
end = heading.end_vim
elif heading.end_of_last_child_vim > line_end and skip_children:
end = heading.end_of_last_child_vim
if mode != u'visual' and not vim.current.buffer[end - 1]:
end -= 1
move_one_character_back = u''
swap_cursor = u'o' if vim.current.window.cursor[0] == line_start else u''
if selection == u'inner' and vim.current.window.cursor[0] != line_start:
h = ORGMODE.get_document().current_heading()
if h:
heading = h
visualmode = u_decode(vim.eval(u'visualmode()')) if mode == u'visual' else u'v'
if line_start == start and line_start != heading.start_vim:
if col_start in (0, 1):
vim.command(u_encode(u'normal! %dgg0%s%dgg$%s%s' % (start, visualmode, end, move_one_character_back, swap_cursor)))
else:
vim.command(u_encode(u'normal! %dgg0%dl%s%dgg$%s%s' % (start, col_start - 1, visualmode, end, move_one_character_back, swap_cursor)))
else:
vim.command(u_encode(u'normal! %dgg0%dl%s%dgg$%s%s' % (start, heading.level + 1, visualmode, end, move_one_character_back, swap_cursor)))
if selection == u'inner':
if mode == u'visual':
return u'OrgInnerHeadingVisual' if not skip_children else u'OrgInnerTreeVisual'
else:
return u'OrgInnerHeadingOperator' if not skip_children else u'OrgInnerTreeOperator'
else:
if mode == u'visual':
return u'OrgOuterHeadingVisual' if not skip_children else u'OrgOuterTreeVisual'
else:
return u'OrgOuterHeadingOperator' if not skip_children else u'OrgOuterTreeOperator'
elif mode == u'visual':
vim.command(u_encode(u'normal! gv'))
# @repeat
@classmethod
@apply_count
def a_heading(cls, selection=u'inner', skip_children=False):
u"""
a heading text object
"""
heading = ORGMODE.get_document().current_heading()
if heading:
if selection != u'inner':
heading = heading if not heading.parent else heading.parent
line_start, col_start = [int(i) for i in vim.eval(u_encode(u'getpos("\'<")'))[1:3]]
line_end, col_end = [int(i) for i in vim.eval(u_encode(u'getpos("\'>")'))[1:3]]
start = line_start
end = line_end
if heading.start_vim < line_start:
start = heading.start_vim
if heading.end_vim > line_end and not skip_children:
end = heading.end_vim
elif heading.end_of_last_child_vim > line_end and skip_children:
end = heading.end_of_last_child_vim
swap_cursor = u'o' if vim.current.window.cursor[0] == line_start else u''
vim.command(u_encode(u'normal! %dgg%s%dgg$%s' % (start, vim.eval(u_encode(u'visualmode()')), end, swap_cursor)))
if selection == u'inner':
return u'OrgAInnerHeadingVisual' if not skip_children else u'OrgAInnerTreeVisual'
else:
return u'OrgAOuterHeadingVisual' if not skip_children else u'OrgAOuterTreeVisual'
else:
vim.command(u_encode(u'normal! gv'))
def register(self):
u"""
Registration of plugin. Key bindings and other initialization should be done.
"""
self.keybindings.append(Keybinding(u'^',
Plug(u'OrgJumpToFirstCharacter', u'%s ORGMODE.plugins[u"Misc"].jump_to_first_character()<CR>' % VIM_PY_CALL)))
self.keybindings.append(Keybinding(u'I',
Plug(u'OrgEditAtFirstCharacter', u'%s ORGMODE.plugins[u"Misc"].edit_at_first_character()<CR>' % VIM_PY_CALL)))
self.keybindings.append(Keybinding(u'ih', Plug(u'OrgInnerHeadingVisual', u':<C-u>%s ORGMODE.plugins[u"Misc"].i_heading()<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'ah', Plug(u'OrgAInnerHeadingVisual', u':<C-u>%s ORGMODE.plugins[u"Misc"].a_heading()<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'Oh', Plug(u'OrgOuterHeadingVisual', u':<C-u>%s ORGMODE.plugins[u"Misc"].i_heading(selection=u"outer")<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'OH', Plug(u'OrgAOuterHeadingVisual', u':<C-u>%s ORGMODE.plugins[u"Misc"].a_heading(selection=u"outer")<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'ih', Plug(u'OrgInnerHeadingOperator', u':<C-u>%s ORGMODE.plugins[u"Misc"].i_heading(mode=u"operator")<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
self.keybindings.append(Keybinding(u'ah', u':normal Vah<CR>', mode=MODE_OPERATOR))
self.keybindings.append(Keybinding(u'Oh', Plug(u'OrgOuterHeadingOperator', ':<C-u>%s ORGMODE.plugins[u"Misc"].i_heading(mode=u"operator", selection=u"outer")<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
self.keybindings.append(Keybinding(u'OH', u':normal VOH<CR>', mode=MODE_OPERATOR))
self.keybindings.append(Keybinding(u'ir', Plug(u'OrgInnerTreeVisual', u':<C-u>%s ORGMODE.plugins[u"Misc"].i_heading(skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'ar', Plug(u'OrgAInnerTreeVisual', u':<C-u>%s ORGMODE.plugins[u"Misc"].a_heading(skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'Or', Plug(u'OrgOuterTreeVisual', u'<:<C-u>%s ORGMODE.plugins[u"Misc"].i_heading(selection=u"outer", skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'OR', Plug(u'OrgAOuterTreeVisual', u':<C-u>%s ORGMODE.plugins[u"Misc"].a_heading(selection=u"outer", skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'ir', Plug(u'OrgInnerTreeOperator', u':<C-u>%s ORGMODE.plugins[u"Misc"].i_heading(mode=u"operator", skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
self.keybindings.append(Keybinding(u'ar', u':normal Var<CR>', mode=MODE_OPERATOR))
self.keybindings.append(Keybinding(u'Or', Plug(u'OrgOuterTreeOperator', u':<C-u>%s ORGMODE.plugins[u"Misc"].i_heading(mode=u"operator", selection=u"outer", skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
self.keybindings.append(Keybinding(u'OR', u':normal VOR<CR>', mode=MODE_OPERATOR))

View File

@@ -0,0 +1,324 @@
# -*- coding: utf-8 -*-
import vim
from orgmode._vim import echo, ORGMODE, apply_count
from orgmode.menu import Submenu, ActionEntry
from orgmode.keybinding import Keybinding, MODE_VISUAL, MODE_OPERATOR, Plug
from orgmode.liborgmode.documents import Direction
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.py_py3_string import *
class Navigator(object):
u""" Implement navigation in org-mode documents """
def __init__(self):
object.__init__(self)
self.menu = ORGMODE.orgmenu + Submenu(u'&Navigate Headings')
self.keybindings = []
@classmethod
@apply_count
def parent(cls, mode):
u"""
Focus parent heading
:returns: parent heading or None
"""
heading = ORGMODE.get_document().current_heading()
if not heading:
if mode == u'visual':
vim.command(u_encode(u'normal! gv'))
else:
echo(u'No heading found')
return
if not heading.parent:
if mode == u'visual':
vim.command(u_encode(u'normal! gv'))
else:
echo(u'No parent heading found')
return
p = heading.parent
if mode == u'visual':
cls._change_visual_selection(heading, p, direction=Direction.BACKWARD, parent=True)
else:
vim.current.window.cursor = (p.start_vim, p.level + 1)
return p
@classmethod
@apply_count
def parent_next_sibling(cls, mode):
u"""
Focus the parent's next sibling
:returns: parent's next sibling heading or None
"""
heading = ORGMODE.get_document().current_heading()
if not heading:
if mode == u'visual':
vim.command(u_encode(u'normal! gv'))
else:
echo(u'No heading found')
return
if not heading.parent or not heading.parent.next_sibling:
if mode == u'visual':
vim.command(u_encode(u'normal! gv'))
else:
echo(u'No parent heading found')
return
ns = heading.parent.next_sibling
if mode == u'visual':
cls._change_visual_selection(heading, ns, direction=Direction.FORWARD, parent=False)
elif mode == u'operator':
vim.current.window.cursor = (ns.start_vim, 0)
else:
vim.current.window.cursor = (ns.start_vim, ns.level + 1)
return ns
@classmethod
def _change_visual_selection(cls, current_heading, heading, direction=Direction.FORWARD, noheadingfound=False, parent=False):
current = vim.current.window.cursor[0]
line_start, col_start = [int(i) for i in vim.eval(u_encode(u'getpos("\'<")'))[1:3]]
line_end, col_end = [int(i) for i in vim.eval(u_encode(u'getpos("\'>")'))[1:3]]
f_start = heading.start_vim
f_end = heading.end_vim
swap_cursor = True
# << |visual start
# selection end >>
if current == line_start:
if (direction == Direction.FORWARD and line_end < f_start) or noheadingfound and not direction == Direction.BACKWARD:
swap_cursor = False
# focus heading HERE
# << |visual start
# selection end >>
# << |visual start
# focus heading HERE
# selection end >>
if f_start < line_start and direction == Direction.BACKWARD:
if current_heading.start_vim < line_start and not parent:
line_start = current_heading.start_vim
else:
line_start = f_start
elif (f_start < line_start or f_start < line_end) and not noheadingfound:
line_start = f_start
# << |visual start
# selection end >>
# focus heading HERE
else:
if direction == Direction.FORWARD:
if line_end < f_start and not line_start == f_start - 1 and current_heading:
# focus end of previous heading instead of beginning of next heading
line_start = line_end
line_end = f_start - 1
else:
# focus end of next heading
line_start = line_end
line_end = f_end
elif direction == Direction.BACKWARD:
if line_end < f_end:
pass
else:
line_start = line_end
line_end = f_end
# << visual start
# selection end| >>
else:
# focus heading HERE
# << visual start
# selection end| >>
if line_start > f_start and line_end > f_end and not parent:
line_end = f_end
swap_cursor = False
elif (line_start > f_start or line_start == f_start) and \
line_end <= f_end and direction == Direction.BACKWARD:
line_end = line_start
line_start = f_start
# << visual start
# selection end and focus heading end HERE| >>
# << visual start
# focus heading HERE
# selection end| >>
# << visual start
# selection end| >>
# focus heading HERE
else:
if direction == Direction.FORWARD:
if line_end < f_start - 1:
# focus end of previous heading instead of beginning of next heading
line_end = f_start - 1
else:
# focus end of next heading
line_end = f_end
else:
line_end = f_end
swap_cursor = False
move_col_start = u'%dl' % (col_start - 1) if (col_start - 1) > 0 and (col_start - 1) < 2000000000 else u''
move_col_end = u'%dl' % (col_end - 1) if (col_end - 1) > 0 and (col_end - 1) < 2000000000 else u''
swap = u'o' if swap_cursor else u''
vim.command(u_encode(u'normal! %dgg%s%s%dgg%s%s' % (line_start, move_col_start, vim.eval(u_encode(u'visualmode()')), line_end, move_col_end, swap)))
@classmethod
def _focus_heading(cls, mode, direction=Direction.FORWARD, skip_children=False):
u"""
Focus next or previous heading in the given direction
:direction: True for next heading, False for previous heading
:returns: next heading or None
"""
d = ORGMODE.get_document()
current_heading = d.current_heading()
heading = current_heading
focus_heading = None
# FIXME this is just a piece of really ugly and unmaintainable code. It
# should be rewritten
if not heading:
if direction == Direction.FORWARD and d.headings \
and vim.current.window.cursor[0] < d.headings[0].start_vim:
# the cursor is in the meta information are, therefore focus
# first heading
focus_heading = d.headings[0]
if not (heading or focus_heading):
if mode == u'visual':
# restore visual selection when no heading was found
vim.command(u_encode(u'normal! gv'))
else:
echo(u'No heading found')
return
elif direction == Direction.BACKWARD:
if vim.current.window.cursor[0] != heading.start_vim:
# the cursor is in the body of the current heading, therefore
# the current heading will be focused
if mode == u'visual':
line_start, col_start = [int(i) for i in
vim.eval(u_encode(u'getpos("\'<")'))[1:3]]
line_end, col_end = [int(i) for i in vim.eval(u_encode(u'getpos("\'>")'))[1:3]]
if line_start >= heading.start_vim and line_end > heading.start_vim:
focus_heading = heading
else:
focus_heading = heading
# so far no heading has been found that the next focus should be on
if not focus_heading:
if not skip_children and direction == Direction.FORWARD and heading.children:
focus_heading = heading.children[0]
elif direction == Direction.FORWARD and heading.next_sibling:
focus_heading = heading.next_sibling
elif direction == Direction.BACKWARD and heading.previous_sibling:
focus_heading = heading.previous_sibling
if not skip_children:
while focus_heading.children:
focus_heading = focus_heading.children[-1]
else:
if direction == Direction.FORWARD:
focus_heading = current_heading.next_heading
else:
focus_heading = current_heading.previous_heading
noheadingfound = False
if not focus_heading:
if mode in (u'visual', u'operator'):
# the cursor seems to be on the last or first heading of this
# document and performs another next/previous operation
focus_heading = heading
noheadingfound = True
else:
if direction == Direction.FORWARD:
echo(u'Already focussing last heading')
else:
echo(u'Already focussing first heading')
return
if mode == u'visual':
cls._change_visual_selection(current_heading, focus_heading, direction=direction, noheadingfound=noheadingfound)
elif mode == u'operator':
if direction == Direction.FORWARD and vim.current.window.cursor[0] >= focus_heading.start_vim:
vim.current.window.cursor = (focus_heading.end_vim, len(u_decode(vim.current.buffer[focus_heading.end])))
else:
vim.current.window.cursor = (focus_heading.start_vim, 0)
else:
vim.current.window.cursor = (focus_heading.start_vim, focus_heading.level + 1)
if noheadingfound:
return
return focus_heading
@classmethod
@apply_count
def previous(cls, mode, skip_children=False):
u"""
Focus previous heading
"""
return cls._focus_heading(mode, direction=Direction.BACKWARD, skip_children=skip_children)
@classmethod
@apply_count
def next(cls, mode, skip_children=False):
u"""
Focus next heading
"""
return cls._focus_heading(mode, direction=Direction.FORWARD, skip_children=skip_children)
def register(self):
# normal mode
self.keybindings.append(Keybinding(u'g{', Plug('OrgJumpToParentNormal',
u'%s ORGMODE.plugins[u"Navigator"].parent(mode=u"normal")<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Up', self.keybindings[-1])
self.keybindings.append(Keybinding(u'g}',
Plug('OrgJumpToParentsSiblingNormal', u'%s ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"normal")<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Down', self.keybindings[-1])
self.keybindings.append(Keybinding(u'{',
Plug(u'OrgJumpToPreviousNormal', u'%s ORGMODE.plugins[u"Navigator"].previous(mode=u"normal")<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Previous', self.keybindings[-1])
self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextNormal',
u'%s ORGMODE.plugins[u"Navigator"].next(mode=u"normal")<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Next', self.keybindings[-1])
# visual mode
self.keybindings.append(Keybinding(u'g{', Plug(u'OrgJumpToParentVisual', u'<Esc>:<C-u>%s ORGMODE.plugins[u"Navigator"].parent(mode=u"visual")<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'g}', Plug('OrgJumpToParentsSiblingVisual', u'<Esc>:<C-u>%s ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"visual")<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'{', Plug(u'OrgJumpToPreviousVisual', u'<Esc>:<C-u>%s ORGMODE.plugins[u"Navigator"].previous(mode=u"visual")<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextVisual', u'<Esc>:<C-u>%s ORGMODE.plugins[u"Navigator"].next(mode=u"visual")<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
# operator-pending mode
self.keybindings.append(Keybinding(u'g{', Plug(u'OrgJumpToParentOperator', u':<C-u>%s ORGMODE.plugins[u"Navigator"].parent(mode=u"operator")<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
self.keybindings.append(Keybinding(u'g}', Plug('OrgJumpToParentsSiblingOperator', u':<C-u>%s ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"operator")<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
self.keybindings.append(Keybinding(u'{', Plug(u'OrgJumpToPreviousOperator', u':<C-u>%s ORGMODE.plugins[u"Navigator"].previous(mode=u"operator")<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextOperator', u':<C-u>%s ORGMODE.plugins[u"Navigator"].next(mode=u"operator")<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
# section wise movement (skip children)
# normal mode
self.keybindings.append(Keybinding(u'[[',
Plug(u'OrgJumpToPreviousSkipChildrenNormal',
u'%s ORGMODE.plugins[u"Navigator"].previous(mode=u"normal", skip_children=True)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'Ne&xt Same Level', self.keybindings[-1])
self.keybindings.append(Keybinding(u']]',
Plug(u'OrgJumpToNextSkipChildrenNormal',
u'%s ORGMODE.plugins[u"Navigator"].next(mode=u"normal", skip_children=True)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'Pre&vious Same Level', self.keybindings[-1])
# visual mode
self.keybindings.append(Keybinding(u'[[', Plug(u'OrgJumpToPreviousSkipChildrenVisual', u'<Esc>:<C-u>%s ORGMODE.plugins[u"Navigator"].previous(mode=u"visual", skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
self.keybindings.append(Keybinding(u']]', Plug(u'OrgJumpToNextSkipChildrenVisual', u'<Esc>:<C-u>%s ORGMODE.plugins[u"Navigator"].next(mode=u"visual", skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_VISUAL)))
# operator-pending mode
self.keybindings.append(Keybinding(u'[[', Plug(u'OrgJumpToPreviousSkipChildrenOperator', u':<C-u>%s ORGMODE.plugins[u"Navigator"].previous(mode=u"operator", skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))
self.keybindings.append(Keybinding(u']]', Plug(u'OrgJumpToNextSkipChildrenOperator', u':<C-u>%s ORGMODE.plugins[u"Navigator"].next(mode=u"operator", skip_children=True)<CR>' % VIM_PY_CALL, mode=MODE_OPERATOR)))

View File

@@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
import vim
from orgmode.liborgmode.headings import Heading
from orgmode._vim import ORGMODE, apply_count
from orgmode import settings
from orgmode.menu import Submenu, ActionEntry
from orgmode.keybinding import Keybinding, Plug, MODE_NORMAL
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.xrange_compatibility import *
from orgmode.py3compat.py_py3_string import *
class ShowHide(object):
u""" Show Hide plugin """
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'&Show Hide')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
@classmethod
def _fold_depth(cls, h):
""" Find the deepest level of open folds
:h: Heading
:returns: Tuple (int - level of open folds, boolean - found fold) or None if h is not a Heading
"""
if not isinstance(h, Heading):
return
if int(vim.eval(u_encode(u'foldclosed(%d)' % h.start_vim))) != -1:
return (h.number_of_parents, True)
res = [h.number_of_parents + 1]
found = False
for c in h.children:
d, f = cls._fold_depth(c)
res.append(d)
found |= f
return (max(res), found)
@classmethod
@apply_count
def toggle_folding(cls, reverse=False):
u""" Toggle folding similar to the way orgmode does
This is just a convenience function, don't hesitate to use the z*
keybindings vim offers to deal with folding!
:reverse: If False open folding by one level otherwise close it by one.
"""
d = ORGMODE.get_document()
heading = d.current_heading()
if not heading:
vim.eval(u_encode(u'feedkeys("<Tab>", "n")'))
return
cursor = vim.current.window.cursor[:]
if int(vim.eval(u_encode(u'foldclosed(%d)' % heading.start_vim))) != -1:
if not reverse:
# open closed fold
p = heading.number_of_parents
if not p:
p = heading.level
vim.command(u_encode(u'normal! %dzo' % p))
else:
# reverse folding opens all folds under the cursor
vim.command(u_encode(u'%d,%dfoldopen!' % (heading.start_vim, heading.end_of_last_child_vim)))
vim.current.window.cursor = cursor
return heading
def open_fold(h):
if h.number_of_parents <= open_depth:
vim.command(u_encode(u'normal! %dgg%dzo' % (h.start_vim, open_depth)))
for c in h.children:
open_fold(c)
def close_fold(h):
for c in h.children:
close_fold(c)
if h.number_of_parents >= open_depth - 1 and \
int(vim.eval(u_encode(u'foldclosed(%d)' % h.start_vim))) == -1:
vim.command(u_encode(u'normal! %dggzc' % (h.start_vim, )))
# find deepest fold
open_depth, found_fold = cls._fold_depth(heading)
if not reverse:
# recursively open folds
if found_fold:
for child in heading.children:
open_fold(child)
else:
vim.command(u_encode(u'%d,%dfoldclose!' % (heading.start_vim, heading.end_of_last_child_vim)))
if heading.number_of_parents:
# restore cursor position, it might have been changed by open_fold
vim.current.window.cursor = cursor
p = heading.number_of_parents
if not p:
p = heading.level
# reopen fold again because the former closing of the fold closed all levels, including parents!
vim.command(u_encode(u'normal! %dzo' % (p, )))
else:
# close the last level of folds
close_fold(heading)
# restore cursor position
vim.current.window.cursor = cursor
return heading
@classmethod
@apply_count
def global_toggle_folding(cls, reverse=False):
""" Toggle folding globally
:reverse: If False open folding by one level otherwise close it by one.
"""
d = ORGMODE.get_document()
if reverse:
foldlevel = int(vim.eval(u_encode(u'&foldlevel')))
if foldlevel == 0:
# open all folds because the user tries to close folds beyond 0
vim.eval(u_encode(u'feedkeys("zR", "n")'))
else:
# vim can reduce the foldlevel on its own
vim.eval(u_encode(u'feedkeys("zm", "n")'))
else:
found = False
for h in d.headings:
res = cls._fold_depth(h)
if res:
found = res[1]
if found:
break
if not found:
# no fold found and the user tries to advance the fold level
# beyond maximum so close everything
vim.eval(u_encode(u'feedkeys("zM", "n")'))
else:
# fold found, vim can increase the foldlevel on its own
vim.eval(u_encode(u'feedkeys("zr", "n")'))
return d
def register(self):
u"""
Registration of plugin. Key bindings and other initialization should be done.
"""
# register plug
self.keybindings.append(Keybinding(u'<Tab>',
Plug(u'OrgToggleFoldingNormal', u'%s ORGMODE.plugins[u"ShowHide"].toggle_folding()<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&Cycle Visibility', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<S-Tab>',
Plug(u'OrgToggleFoldingReverse', u'%s ORGMODE.plugins[u"ShowHide"].toggle_folding(reverse=True)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'Cycle Visibility &Reverse', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<localleader>.',
Plug(u'OrgGlobalToggleFoldingNormal', u'%s ORGMODE.plugins[u"ShowHide"].global_toggle_folding()<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'Cycle Visibility &Globally', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<localleader>,',
Plug(u'OrgGlobalToggleFoldingReverse',
u'%s ORGMODE.plugins[u"ShowHide"].global_toggle_folding(reverse=True)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'Cycle Visibility Reverse G&lobally', self.keybindings[-1])
for i in range(0, 10):
self.keybindings.append(Keybinding(u'<localleader>%d' % (i, ), u'zM:set fdl=%d<CR>' % i, mode=MODE_NORMAL))

View File

@@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
import vim
from orgmode._vim import ORGMODE, repeat
from orgmode.menu import Submenu, ActionEntry
from orgmode.keybinding import Keybinding, Plug, Command
from orgmode import settings
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.py_py3_string import *
class TagsProperties(object):
u""" TagsProperties plugin """
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'&TAGS and Properties')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
# commands for this plugin
self.commands = []
@classmethod
def complete_tags(cls):
u""" build a list of tags and store it in variable b:org_tag_completion
"""
d = ORGMODE.get_document()
heading = d.current_heading()
if not heading:
return
leading_portion = u_decode(vim.eval(u'a:ArgLead'))
cursor = int(vim.eval(u'a:CursorPos'))
# extract currently completed tag
idx_orig = leading_portion.rfind(u':', 0, cursor)
if idx_orig == -1:
idx = 0
else:
idx = idx_orig
current_tag = leading_portion[idx: cursor].lstrip(u':')
head = leading_portion[:idx + 1]
if idx_orig == -1:
head = u''
tail = leading_portion[cursor:]
# extract all tags of the current file
all_tags = set()
for h in d.all_headings():
for t in h.tags:
all_tags.add(t)
ignorecase = bool(int(settings.get(u'org_tag_completion_ignorecase', int(vim.eval(u'&ignorecase')))))
possible_tags = []
# TODO current tags never used...
current_tags = heading.tags
for t in all_tags:
if ignorecase:
if t.lower().startswith(current_tag.lower()):
possible_tags.append(t)
elif t.startswith(current_tag):
possible_tags.append(t)
vim.command(u_encode(u'let b:org_complete_tags = [%s]' % u', '.join([u'"%s%s:%s"' % (head, i, tail) for i in possible_tags])))
@classmethod
@repeat
def set_tags(cls):
u""" Set tags for current heading
"""
d = ORGMODE.get_document()
heading = d.current_heading()
if not heading:
return
# retrieve tags
res = None
if heading.tags:
res = vim.eval(u'input("Tags: ", ":%s:", "customlist,Org_complete_tags")' % u':'.join(heading.tags))
else:
res = vim.eval(u'input("Tags: ", "", "customlist,Org_complete_tags")')
if res is None:
# user pressed <Esc> abort any further processing
return
# remove empty tags
heading.tags = [x for x in u_decode(res).strip().strip(u':').split(u':') if x.strip() != u'']
d.write()
return u'OrgSetTags'
@classmethod
def find_tags(cls):
""" Find tags in current file
"""
tags = vim.eval(u'input("Find Tags: ", "", "customlist,Org_complete_tags")')
if tags is None:
# user pressed <Esc> abort any further processing
return
tags = [x for x in u_decode(tags).strip().strip(u':').split(u':') if x.strip() != u'']
if tags:
searchstring = u'\\('
first = True
for t1 in tags:
if first:
first = False
searchstring += u'%s' % t1
else:
searchstring += u'\\|%s' % t1
for t2 in tags:
if t1 == t2:
continue
searchstring += u'\\(:[a-zA-Z:]*\\)\\?:%s' % t2
searchstring += u'\\)'
vim.command(u'/\\zs:%s:\\ze' % searchstring)
return u'OrgFindTags'
@classmethod
def realign_tags(cls):
u"""
Updates tags when user finished editing a heading
"""
d = ORGMODE.get_document(allow_dirty=True)
heading = d.find_current_heading()
if not heading:
return
if vim.current.window.cursor[0] == heading.start_vim:
heading.set_dirty_heading()
d.write_heading(heading, including_children=False)
@classmethod
def realign_all_tags(cls):
u"""
Updates tags when user finishes editing a heading
"""
d = ORGMODE.get_document()
for heading in d.all_headings():
heading.set_dirty_heading()
d.write()
def register(self):
u"""
Registration of plugin. Key bindings and other initialization should be done.
"""
# an Action menu entry which binds "keybinding" to action ":action"
settings.set(u'org_tag_column', vim.eval(u'&textwidth'))
settings.set(u'org_tag_completion_ignorecase', int(vim.eval(u'&ignorecase')))
cmd = Command(
u'OrgSetTags',
u'%s ORGMODE.plugins[u"TagsProperties"].set_tags()' % VIM_PY_CALL)
self.commands.append(cmd)
keybinding = Keybinding(
u'<localleader>st',
Plug(u'OrgSetTags', cmd))
self.keybindings.append(keybinding)
self.menu + ActionEntry(u'Set &Tags', keybinding)
cmd = Command(
u'OrgFindTags',
u'%s ORGMODE.plugins[u"TagsProperties"].find_tags()' % VIM_PY_CALL)
self.commands.append(cmd)
keybinding = Keybinding(
u'<localleader>ft',
Plug(u'OrgFindTags', cmd))
self.keybindings.append(keybinding)
self.menu + ActionEntry(u'&Find Tags', keybinding)
cmd = Command(
u'OrgTagsRealign',
u"%s ORGMODE.plugins[u'TagsProperties'].realign_all_tags()" % VIM_PY_CALL)
self.commands.append(cmd)
# workaround to align tags when user is leaving insert mode
vim.command(u_encode(u"function Org_complete_tags(ArgLead, CmdLine, CursorPos)\n"
+ sys.executable.split('/')[-1] + u""" << EOF
ORGMODE.plugins[u'TagsProperties'].complete_tags()
EOF
if exists('b:org_complete_tags')
let tmp = b:org_complete_tags
unlet b:org_complete_tags
return tmp
else
return []
endif
endfunction"""))
vim.command(u_encode(u"""function Org_realign_tags_on_insert_leave()
if !exists('b:org_complete_tag_on_insertleave_au')
:au orgmode InsertLeave <buffer> %s ORGMODE.plugins[u'TagsProperties'].realign_tags()
let b:org_complete_tag_on_insertleave_au = 1
endif
endfunction""" % VIM_PY_CALL))
# this is for all org files opened after this file
vim.command(u_encode(u"au orgmode FileType org call Org_realign_tags_on_insert_leave()"))
# this is for the current file
vim.command(u_encode(u"call Org_realign_tags_on_insert_leave()"))

View File

@@ -0,0 +1,344 @@
# -*- coding: utf-8 -*-
import vim
import re
import itertools as it
from orgmode._vim import echom, ORGMODE, apply_count, repeat, realign_tags
from orgmode import settings
from orgmode.liborgmode.base import Direction
from orgmode.menu import Submenu, ActionEntry
from orgmode.keybinding import Keybinding, Plug
from orgmode.exceptions import PluginError
# temporary todo states for different orgmode buffers
ORGTODOSTATES = {}
from orgmode.py3compat.xrange_compatibility import *
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
from orgmode.py3compat.py_py3_string import *
def split_access_key(t, sub=None):
u""" Split access key
Args:
t (str): Todo state
sub: A value that will be returned instead of access key if there was
not access key
Returns:
tuple: Todo state and access key separated (TODO, ACCESS_KEY)
Example:
>>> split_access_key('TODO(t)')
>>> ('TODO', '(t)')
>>> split_access_key('WANT', sub='(hi)')
>>> ('WANT', '(hi)')
"""
if type(t) != unicode:
echom("String must be unicode")
return (None, None)
idx = t.find(u'(')
v, k = (t, sub)
if idx != -1 and t[idx + 1:-1]:
v, k = (t[:idx], t[idx + 1:-1])
return (v, k)
class Todo(object):
u"""
Todo plugin.
Description taken from orgmode.org:
You can use TODO keywords to indicate different sequential states in the
process of working on an item, for example:
["TODO", "FEEDBACK", "VERIFY", "|", "DONE", "DELEGATED"]
The vertical bar separates the TODO keywords (states that need action) from
the DONE states (which need no further action). If you don't provide the
separator bar, the last state is used as the DONE state. With this setup,
the command ``,d`` will cycle an entry from TODO to FEEDBACK, then to
VERIFY, and finally to DONE and DELEGATED.
"""
def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'&TODO Lists')
# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []
@classmethod
def _process_all_states(cls, all_states):
u""" verify if states defined by user is valid.
Return cleaned_todo and flattened if is. Raise Exception if not.
Valid checking:
* no two state share a same name
"""
# TODO Write tests. -- Ron89
cleaned_todos = [[
split_access_key(todo)[0] for todo in it.chain.from_iterable(x)]
for x in all_states] + [[None]]
flattened_todos = list(it.chain.from_iterable(cleaned_todos))
if len(flattened_todos) != len(set(flattened_todos)):
raise PluginError(u"Duplicate names detected in TODO keyword list. Please examine `g/b:org_todo_keywords`")
# TODO This is the case when there are 2 todo states with the same
# name. It should be handled by making a simple class to hold TODO
# states, which would avoid mixing 2 todo states with the same name
# since they would have a different reference (but same content),
# albeit this can fail because python optimizes short strings (i.e.
# they hold the same ref) so care should be taken in implementation
return (cleaned_todos, flattened_todos)
@classmethod
def _get_next_state(
cls, current_state, all_states, direction=Direction.FORWARD,
next_set=False):
u""" Get the next todo state
Args:
current_state (str): The current todo state
all_states (list): A list containing all todo states within
sublists. The todo states may contain access keys
direction: Direction of state or keyword set change (forward or
backward)
next_set: Advance to the next keyword set in defined direction.
Returns:
str or None: next todo state, or None if there is no next state.
Note: all_states should have the form of:
[(['TODO(t)'], ['DONE(d)']),
(['REPORT(r)', 'BUG(b)', 'KNOWNCAUSE(k)'], ['FIXED(f)']),
([], ['CANCELED(c)'])]
"""
cleaned_todos, flattened_todos = cls._process_all_states(all_states)
# backward direction should really be -1 not 2
next_dir = -1 if direction == Direction.BACKWARD else 1
# work only with top level index
if next_set:
top_set = next((
todo_set[0] for todo_set in enumerate(cleaned_todos)
if current_state in todo_set[1]), -1)
ind = (top_set + next_dir) % len(cleaned_todos)
if ind != len(cleaned_todos) - 1:
echom("Using set: %s" % str(all_states[ind]))
else:
echom("Keyword removed.")
return cleaned_todos[ind][0]
# No next set, cycle around everything
else:
ind = next((
todo_iter[0] for todo_iter in enumerate(flattened_todos)
if todo_iter[1] == current_state), -1)
return flattened_todos[(ind + next_dir) % len(flattened_todos)]
@classmethod
@realign_tags
@repeat
@apply_count
def toggle_todo_state(
cls, direction=Direction.FORWARD, interactive=False, next_set=False):
u""" Toggle state of TODO item
:returns: The changed heading
"""
d = ORGMODE.get_document(allow_dirty=True)
# get heading
heading = d.find_current_heading()
if not heading:
vim.eval(u'feedkeys("^", "n")')
return
todo_states = d.get_todo_states(strip_access_key=False)
# get todo states
if not todo_states:
echom(u'No todo keywords configured.')
return
current_state = heading.todo
# get new state interactively
if interactive:
# determine position of the interactive prompt
prompt_pos = settings.get(u'org_todo_prompt_position', u'botright')
if prompt_pos not in [u'botright', u'topleft']:
prompt_pos = u'botright'
# pass todo states to new window
ORGTODOSTATES[d.bufnr] = todo_states
settings.set(
u'org_current_state_%d' % d.bufnr,
current_state if current_state is not None else u'', overwrite=True)
todo_buffer_exists = bool(int(vim.eval(u_encode(
u'bufexists("org:todo/%d")' % (d.bufnr, )))))
if todo_buffer_exists:
# if the buffer already exists, reuse it
vim.command(u_encode(
u'%s sbuffer org:todo/%d' % (prompt_pos, d.bufnr, )))
else:
# create a new window
vim.command(u_encode(
u'keepalt %s %dsplit org:todo/%d' % (prompt_pos, len(todo_states), d.bufnr)))
else:
new_state = Todo._get_next_state(
current_state, todo_states, direction=direction,
next_set=next_set)
cls.set_todo_state(new_state)
# plug
plug = u'OrgTodoForward'
if direction == Direction.BACKWARD:
plug = u'OrgTodoBackward'
return plug
@classmethod
def set_todo_state(cls, state):
u""" Set todo state for buffer.
:bufnr: Number of buffer the todo state should be updated for
:state: The new todo state
"""
lineno, colno = vim.current.window.cursor
d = ORGMODE.get_document(allow_dirty=True)
heading = d.find_current_heading()
if not heading:
return
current_state = heading.todo
# set new headline
heading.todo = state
d.write_heading(heading)
# move cursor along with the inserted state only when current position
# is in the heading; otherwite do nothing
if heading.start_vim == lineno and colno > heading.level:
if current_state is not None and \
colno <= heading.level + len(current_state):
# the cursor is actually on the todo keyword
# move it back to the beginning of the keyword in that case
vim.current.window.cursor = (lineno, heading.level + 1)
else:
# the cursor is somewhere in the text, move it along
if current_state is None and state is None:
offset = 0
elif current_state is None and state is not None:
offset = len(state) + 1
elif current_state is not None and state is None:
offset = -len(current_state) - 1
else:
offset = len(state) - len(current_state)
vim.current.window.cursor = (lineno, colno + offset)
@classmethod
def init_org_todo(cls):
u""" Initialize org todo selection window.
"""
bufnr = int(re.findall('\d+$',vim.current.buffer.name)[0])
all_states = ORGTODOSTATES.get(bufnr, None)
vim_commands = [
u'let g:org_sav_timeoutlen=&timeoutlen',
u'au orgmode BufEnter <buffer> :if ! exists("g:org_sav_timeoutlen")|let g:org_sav_timeoutlen=&timeoutlen|set timeoutlen=1|endif',
u'au orgmode BufLeave <buffer> :if exists("g:org_sav_timeoutlen")|let &timeoutlen=g:org_sav_timeoutlen|unlet g:org_sav_timeoutlen|endif',
u'setlocal nolist tabstop=16 buftype=nofile timeout timeoutlen=1 winfixheight',
u'setlocal statusline=Org\\ todo\\ (%s)' % vim.eval(u_encode(u'fnameescape(fnamemodify(bufname(%d), ":t"))' % bufnr)),
u'nnoremap <silent> <buffer> <Esc> :%sbw<CR>' % vim.eval(u_encode(u'bufnr("%")')),
u'nnoremap <silent> <buffer> <CR> :let g:org_state = fnameescape(expand("<cword>"))<Bar>bw<Bar>exec "%s ORGMODE.plugins[u\'Todo\'].set_todo_state(\'".g:org_state."\')"<Bar>unlet! g:org_state<CR>' % VIM_PY_CALL,
]
# because timeoutlen can only be set globally it needs to be stored and
# restored later
# make window a scratch window and set the statusline differently
for cmd in vim_commands:
vim.command(u_encode(cmd))
if all_states is None:
vim.command(u_encode(u'bw'))
echom(u'No todo states available for buffer %s' % vim.current.buffer.name)
for idx, state in enumerate(all_states):
pairs = [split_access_key(x, sub=u' ') for x in it.chain(*state)]
line = u'\t'.join(u''.join((u'[%s] ' % x[1], x[0])) for x in pairs)
vim.current.buffer.append(u_encode(line))
for todo, key in pairs:
# FIXME if double key is used for access modified this doesn't work
vim.command(u_encode(u'nnoremap <silent> <buffer> %s :bw<CR><c-w><c-p>%s ORGMODE.plugins[u"Todo"].set_todo_state("%s")<CR>' % (key, VIM_PY_CALL, u_decode(todo))))
# position the cursor of the current todo item
vim.command(u_encode(u'normal! G'))
current_state = settings.unset(u'org_current_state_%d' % bufnr)
if current_state is not None and current_state != '':
for i, buf in enumerate(vim.current.buffer):
idx = buf.find(current_state)
if idx != -1:
vim.current.window.cursor = (i + 1, idx)
break
else:
vim.current.window.cursor = (2, 4)
# finally make buffer non modifiable
vim.command(u_encode(u'setfiletype orgtodo'))
vim.command(u_encode(u'setlocal nomodifiable'))
# remove temporary todo states for the current buffer
del ORGTODOSTATES[bufnr]
def register(self):
u"""
Registration of plugin. Key bindings and other initialization should be done.
"""
self.keybindings.append(Keybinding(u'<localleader>ct', Plug(
u'OrgTodoToggleNonInteractive',
u'%s ORGMODE.plugins[u"Todo"].toggle_todo_state(interactive=False)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&TODO/DONE/-', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<localleader>d', Plug(
u'OrgTodoToggleInteractive',
u'%s ORGMODE.plugins[u"Todo"].toggle_todo_state(interactive=True)<CR>' % VIM_PY_CALL)))
self.menu + ActionEntry(u'&TODO/DONE/- (interactive)', self.keybindings[-1])
# add submenu
submenu = self.menu + Submenu(u'Select &keyword')
self.keybindings.append(Keybinding(u'<S-Right>', Plug(
u'OrgTodoForward',
u'%s ORGMODE.plugins[u"Todo"].toggle_todo_state()<CR>' % VIM_PY_CALL)))
submenu + ActionEntry(u'&Next keyword', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<S-Left>', Plug(
u'OrgTodoBackward',
u'%s ORGMODE.plugins[u"Todo"].toggle_todo_state(direction=2)<CR>' % VIM_PY_CALL)))
submenu + ActionEntry(u'&Previous keyword', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<C-S-Right>', Plug(
u'OrgTodoSetForward',
u'%s ORGMODE.plugins[u"Todo"].toggle_todo_state(next_set=True)<CR>' % VIM_PY_CALL)))
submenu + ActionEntry(u'Next keyword &set', self.keybindings[-1])
self.keybindings.append(Keybinding(u'<C-S-Left>', Plug(
u'OrgTodoSetBackward',
u'%s ORGMODE.plugins[u"Todo"].toggle_todo_state(direction=2, next_set=True)<CR>' % VIM_PY_CALL)))
submenu + ActionEntry(u'Previous &keyword set', self.keybindings[-1])
settings.set(u'org_todo_keywords', [u_encode(u'TODO'), u_encode(u'|'), u_encode(u'DONE')])
settings.set(u'org_todo_prompt_position', u'botright')
vim.command(u_encode(u'au orgmode BufReadCmd org:todo/* %s ORGMODE.plugins[u"Todo"].init_org_todo()' % VIM_PY_CALL))

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,11 @@
import sys
if sys.version_info < (3,):
def u_encode(string):
return string.encode('utf8')
def u_decode(string):
return string.decode('utf8')
else:
def u_encode(string):
return string
def u_decode(string):
return string

View File

@@ -0,0 +1,17 @@
import sys
from string import Formatter
if sys.version_info < (3,):
VIM_PY_CALL = u':py'
else:
VIM_PY_CALL = u':py3'
class NoneAsEmptyFormatter(Formatter):
def get_value(self, key, args, kwargs):
v = super().get_value(key, args, kwargs)
return '' if v is None else v
fmt = NoneAsEmptyFormatter()

View File

@@ -0,0 +1,4 @@
try:
unicode
except NameError:
basestring = unicode = str

View File

@@ -0,0 +1,4 @@
try:
from __builtin__ import xrange as range
except:
pass

View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
import vim
import sys
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
SCOPE_ALL = 1
# for all vim-orgmode buffers
SCOPE_GLOBAL = 2
# just for the current buffer - has priority before the global settings
SCOPE_BUFFER = 4
VARIABLE_LEADER = {SCOPE_GLOBAL: u'g', SCOPE_BUFFER: u'b'}
u""" Evaluate and store settings """
def get(setting, default=None, scope=SCOPE_ALL):
u""" Evaluate setting in scope of the current buffer,
globally and also from the contents of the current buffer
WARNING: Only string values are converted to unicode. If a different value
is received, e.g. a list or dict, no conversion is done.
:setting: name of the variable to evaluate
:default: default value in case the variable is empty
:returns: variable value
"""
# TODO first read setting from org file which take precedence over vim
# variable settings
if (scope & SCOPE_ALL | SCOPE_BUFFER) and \
int(vim.eval(u_encode(u'exists("b:%s")' % setting))):
res = vim.eval(u_encode(u"b:%s" % setting))
if type(res) in (unicode, str):
return u_decode(res)
return res
elif (scope & SCOPE_ALL | SCOPE_GLOBAL) and \
int(vim.eval(u_encode(u'exists("g:%s")' % setting))):
res = vim.eval(u_encode(u"g:%s" % setting))
if type(res) in (unicode, str):
return u_decode(res)
return res
return default
def set(setting, value, scope=SCOPE_GLOBAL, overwrite=False):
u""" Store setting in the defined scope
WARNING: For the return value, only string are converted to unicode. If a
different value is received by vim.eval, e.g. a list or dict, no conversion
is done.
:setting: name of the setting
:value: the actual value, repr is called on the value to create a string
representation
:scope: the scope o the setting/variable
:overwrite: overwrite existing settings (probably user defined settings)
:returns: the new value in case of overwrite==False the current value
"""
if (not overwrite) and (
int(vim.eval(u_encode(u'exists("%s:%s")' % \
(VARIABLE_LEADER[scope], setting))))):
res = vim.eval(
u_encode(u'%s:%s' % (VARIABLE_LEADER[scope], setting)))
if type(res) in (unicode, str):
return u_decode(res)
return res
v = repr(value)
if type(value) == unicode and sys.version_info < (3,):
# strip leading u of unicode string representations
v = v[1:]
cmd = u'let %s:%s = %s' % (VARIABLE_LEADER[scope], setting, v)
vim.command(u_encode(cmd))
return value
def unset(setting, scope=SCOPE_GLOBAL):
u""" Unset setting in the defined scope
:setting: name of the setting
:scope: the scope o the setting/variable
:returns: last value of setting
"""
value = get(setting, scope=scope)
cmd = u'unlet! %s:%s' % (VARIABLE_LEADER[scope], setting)
vim.command(u_encode(cmd))
return value

View File

@@ -0,0 +1,500 @@
# -*- coding: utf-8 -*-
"""
vimbuffer
~~~~~~~~~~
VimBuffer and VimBufferContent are the interface between liborgmode and
vim.
VimBuffer extends the liborgmode.document.Document().
Document() is just a general implementation for loading an org file. It
has no interface to an actual file or vim buffer. This is the task of
vimbuffer.VimBuffer(). It is the interfaces to vim. The main tasks for
VimBuffer are to provide read and write access to a real vim buffer.
VimBufferContent is a helper class for VimBuffer. Basically, it hides the
details of encoding - everything read from or written to VimBufferContent
is UTF-8.
"""
try:
from collections import UserList
except:
from UserList import UserList
import vim
from orgmode import settings
from orgmode.exceptions import BufferNotFound, BufferNotInSync
from orgmode.liborgmode.documents import Document, MultiPurposeList, Direction
from orgmode.liborgmode.headings import Heading
from orgmode.py3compat.encode_compatibility import *
from orgmode.py3compat.unicode_compatibility import *
class VimBuffer(Document):
def __init__(self, bufnr=0):
u"""
:bufnr: 0: current buffer, every other number refers to another buffer
"""
Document.__init__(self)
self._bufnr = vim.current.buffer.number if bufnr == 0 else bufnr
self._changedtick = -1
self._cached_heading = None
if self._bufnr == vim.current.buffer.number:
self._content = VimBufferContent(vim.current.buffer)
else:
_buffer = None
for b in vim.buffers:
if self._bufnr == b.number:
_buffer = b
break
if not _buffer:
raise BufferNotFound(u'Unable to locate buffer number #%d' % self._bufnr)
self._content = VimBufferContent(_buffer)
self.update_changedtick()
self._orig_changedtick = self._changedtick
@property
def tabstop(self):
return int(vim.eval(u_encode(u'&ts')))
@property
def tag_column(self):
return int(settings.get(u'org_tag_column', u'77'))
@property
def is_insync(self):
if self._changedtick == self._orig_changedtick:
self.update_changedtick()
return self._changedtick == self._orig_changedtick
@property
def bufnr(self):
u"""
:returns: The buffer's number for the current document
"""
return self._bufnr
@property
def changedtick(self):
u""" Number of changes in vimbuffer """
return self._changedtick
@changedtick.setter
def changedtick(self, value):
self._changedtick = value
def get_todo_states(self, strip_access_key=True):
u""" Returns a list containing a tuple of two lists of allowed todo
states split by todo and done states. Multiple todo-done state
sequences can be defined.
:returns: [([todo states], [done states]), ..]
"""
states = settings.get(u'org_todo_keywords', [])
# TODO this function gets called too many times when change of state of
# one todo is triggered, check with:
# print(states)
# this should be changed by saving todo states into some var and only
# if new states are set hook should be called to register them again
# into a property
# TODO move this to documents.py, it is all tangled up like this, no
# structure...
if type(states) not in (list, tuple):
return []
def parse_states(s, stop=0):
res = []
if not s:
return res
if type(s[0]) in (unicode, str):
r = []
for i in s:
_i = i
if type(_i) == str:
_i = u_decode(_i)
if type(_i) == unicode and _i:
if strip_access_key and u'(' in _i:
_i = _i[:_i.index(u'(')]
if _i:
r.append(_i)
else:
r.append(_i)
if not u'|' in r:
if not stop:
res.append((r[:-1], [r[-1]]))
else:
res = (r[:-1], [r[-1]])
else:
seperator_pos = r.index(u'|')
if not stop:
res.append((r[0:seperator_pos], r[seperator_pos + 1:]))
else:
res = (r[0:seperator_pos], r[seperator_pos + 1:])
elif type(s) in (list, tuple) and not stop:
for i in s:
r = parse_states(i, stop=1)
if r:
res.append(r)
return res
return parse_states(states)
def update_changedtick(self):
if self.bufnr == vim.current.buffer.number:
self._changedtick = int(vim.eval(u_encode(u'b:changedtick')))
else:
vim.command(u_encode(u'unlet! g:org_changedtick | let g:org_lz = &lz | let g:org_hidden = &hidden | set lz hidden'))
# TODO is this likely to fail? maybe some error hangling should be added
vim.command(u_encode(u'keepalt buffer %d | let g:org_changedtick = b:changedtick | buffer %d' % \
(self.bufnr, vim.current.buffer.number)))
vim.command(u_encode(u'let &lz = g:org_lz | let &hidden = g:org_hidden | unlet! g:org_lz g:org_hidden | redraw'))
self._changedtick = int(vim.eval(u_encode(u'g:org_changedtick')))
def write(self):
u""" write the changes to the vim buffer
:returns: True if something was written, otherwise False
"""
if not self.is_dirty:
return False
self.update_changedtick()
if not self.is_insync:
raise BufferNotInSync(u'Buffer is not in sync with vim!')
# write meta information
if self.is_dirty_meta_information:
meta_end = 0 if self._orig_meta_information_len is None else self._orig_meta_information_len
self._content[:meta_end] = self.meta_information
self._orig_meta_information_len = len(self.meta_information)
# remove deleted headings
already_deleted = []
for h in sorted(self._deleted_headings, key=lambda x: x._orig_start, reverse=True):
if h._orig_start is not None and h._orig_start not in already_deleted:
# this is a heading that actually exists on the buffer and it
# needs to be removed
del self._content[h._orig_start:h._orig_start + h._orig_len]
already_deleted.append(h._orig_start)
del self._deleted_headings[:]
del already_deleted
# update changed headings and add new headings
for h in self.all_headings():
if h.is_dirty:
vim.current.buffer.append("") # workaround for neovim bug
if h._orig_start is not None:
# this is a heading that existed before and was changed. It
# needs to be replaced
if h.is_dirty_heading:
self._content[h.start:h.start + 1] = [unicode(h)]
if h.is_dirty_body:
self._content[h.start + 1:h.start + h._orig_len] = h.body
else:
# this is a new heading. It needs to be inserted
self._content[h.start:h.start] = [unicode(h)] + h.body
del vim.current.buffer[-1] # restore workaround for neovim bug
h._dirty_heading = False
h._dirty_body = False
# for all headings the length and start offset needs to be updated
h._orig_start = h.start
h._orig_len = len(h)
self._dirty_meta_information = False
self._dirty_document = False
self.update_changedtick()
self._orig_changedtick = self._changedtick
return True
def write_heading(self, heading, including_children=True):
""" WARNING: use this function only when you know what you are doing!
This function writes a heading to the vim buffer. It offers performance
advantages over the regular write() function. This advantage is
combined with no sanity checks! Whenever you use this function, make
sure the heading you are writing contains the right offsets
(Heading._orig_start, Heading._orig_len).
Usage example:
# Retrieve a potentially dirty document
d = ORGMODE.get_document(allow_dirty=True)
# Don't rely on the DOM, retrieve the heading afresh
h = d.find_heading(direction=Direction.FORWARD, position=100)
# Update tags
h.tags = ['tag1', 'tag2']
# Write the heading
d.write_heading(h)
This function can't be used to delete a heading!
:heading: Write this heading with to the vim buffer
:including_children: Also include children in the update
:returns The written heading
"""
if including_children and heading.children:
for child in heading.children[::-1]:
self.write_heading(child, including_children)
if heading.is_dirty:
if heading._orig_start is not None:
# this is a heading that existed before and was changed. It
# needs to be replaced
if heading.is_dirty_heading:
self._content[heading._orig_start:heading._orig_start + 1] = [unicode(heading)]
if heading.is_dirty_body:
self._content[heading._orig_start + 1:heading._orig_start + heading._orig_len] = heading.body
else:
# this is a new heading. It needs to be inserted
raise ValueError('Heading must contain the attribute _orig_start! %s' % heading)
heading._dirty_heading = False
heading._dirty_body = False
# for all headings the length offset needs to be updated
heading._orig_len = len(heading)
return heading
def write_checkbox(self, checkbox, including_children=True):
if including_children and checkbox.children:
for child in checkbox.children[::-1]:
self.write_checkbox(child, including_children)
if checkbox.is_dirty:
if checkbox._orig_start is not None:
# this is a heading that existed before and was changed. It
# needs to be replaced
# print "checkbox is dirty? " + str(checkbox.is_dirty_checkbox)
# print checkbox
if checkbox.is_dirty_checkbox:
self._content[checkbox._orig_start:checkbox._orig_start + 1] = [unicode(checkbox)]
if checkbox.is_dirty_body:
self._content[checkbox._orig_start + 1:checkbox._orig_start + checkbox._orig_len] = checkbox.body
else:
# this is a new checkbox. It needs to be inserted
raise ValueError('Checkbox must contain the attribute _orig_start! %s' % checkbox)
checkbox._dirty_checkbox = False
checkbox._dirty_body = False
# for all headings the length offset needs to be updated
checkbox._orig_len = len(checkbox)
return checkbox
def write_checkboxes(self, checkboxes):
pass
def previous_heading(self, position=None):
u""" Find the next heading (search forward) and return the related object
:returns: Heading object or None
"""
h = self.current_heading(position=position)
if h:
return h.previous_heading
def current_heading(self, position=None):
u""" Find the current heading (search backward) and return the related object
:returns: Heading object or None
"""
if position is None:
position = vim.current.window.cursor[0] - 1
if not self.headings:
return
def binaryFindInDocument():
hi = len(self.headings)
lo = 0
while lo < hi:
mid = (lo+hi)//2
h = self.headings[mid]
if h.end_of_last_child < position:
lo = mid + 1
elif h.start > position:
hi = mid
else:
return binaryFindHeading(h)
def binaryFindHeading(heading):
if not heading.children or heading.end >= position:
return heading
hi = len(heading.children)
lo = 0
while lo < hi:
mid = (lo+hi)//2
h = heading.children[mid]
if h.end_of_last_child < position:
lo = mid + 1
elif h.start > position:
hi = mid
else:
return binaryFindHeading(h)
# look at the cache to find the heading
h_tmp = self._cached_heading
if h_tmp is not None:
if h_tmp.end_of_last_child > position and \
h_tmp.start < position:
if h_tmp.end < position:
self._cached_heading = binaryFindHeading(h_tmp)
return self._cached_heading
self._cached_heading = binaryFindInDocument()
return self._cached_heading
def next_heading(self, position=None):
u""" Find the next heading (search forward) and return the related object
:returns: Heading object or None
"""
h = self.current_heading(position=position)
if h:
return h.next_heading
def find_current_heading(self, position=None, heading=Heading):
u""" Find the next heading backwards from the position of the cursor.
The difference to the function current_heading is that the returned
object is not built into the DOM. In case the DOM doesn't exist or is
out of sync this function is much faster in fetching the current
heading.
:position: The position to start the search from
:heading: The base class for the returned heading
:returns: Heading object or None
"""
return self.find_heading(vim.current.window.cursor[0] - 1 \
if position is None else position, \
direction=Direction.BACKWARD, heading=heading, \
connect_with_document=False)
class VimBufferContent(MultiPurposeList):
u""" Vim Buffer Content is a UTF-8 wrapper around a vim buffer. When
retrieving or setting items in the buffer an automatic conversion is
performed.
This ensures UTF-8 usage on the side of liborgmode and the vim plugin
vim-orgmode.
"""
def __init__(self, vimbuffer, on_change=None):
MultiPurposeList.__init__(self, on_change=on_change)
# replace data with vimbuffer to make operations change the actual
# buffer
self.data = vimbuffer
def __contains__(self, item):
i = item
if type(i) is unicode:
i = u_encode(item)
return MultiPurposeList.__contains__(self, i)
def __getitem__(self, i):
if isinstance(i, slice):
return [u_decode(item) if type(item) is str else item \
for item in MultiPurposeList.__getitem__(self, i)]
else:
item = MultiPurposeList.__getitem__(self, i)
if type(item) is str:
return u_decode(item)
return item
def __setitem__(self, i, item):
if isinstance(i, slice):
o = []
o_tmp = item
if type(o_tmp) not in (list, tuple) and not isinstance(o_tmp, UserList):
o_tmp = list(o_tmp)
for item in o_tmp:
if type(item) == unicode:
o.append(u_encode(item))
else:
o.append(item)
MultiPurposeList.__setitem__(self, i, o)
else:
_i = item
if type(_i) is unicode:
_i = u_encode(item)
# TODO: fix this bug properly, it is really strange that it fails on
# python3 without it. Problem is that when _i = ['* '] it fails in
# UserList.__setitem__() but if it is changed in debuggr in __setitem__
# like item[0] = '* ' it works, hence this is some quirk with unicode
# stuff but very likely vim 7.4 BUG too.
if isinstance(_i, UserList) and sys.version_info > (3, ):
_i = [s.encode('utf8').decode('utf8') for s in _i]
MultiPurposeList.__setitem__(self, i, _i)
def __add__(self, other):
raise NotImplementedError()
# TODO: implement me
if isinstance(other, UserList):
return self.__class__(self.data + other.data)
elif isinstance(other, type(self.data)):
return self.__class__(self.data + other)
else:
return self.__class__(self.data + list(other))
def __radd__(self, other):
raise NotImplementedError()
# TODO: implement me
if isinstance(other, UserList):
return self.__class__(other.data + self.data)
elif isinstance(other, type(self.data)):
return self.__class__(other + self.data)
else:
return self.__class__(list(other) + self.data)
def __iadd__(self, other):
o = []
o_tmp = other
if type(o_tmp) not in (list, tuple) and not isinstance(o_tmp, UserList):
o_tmp = list(o_tmp)
for i in o_tmp:
if type(i) is unicode:
o.append(u_encode(i))
else:
o.append(i)
return MultiPurposeList.__iadd__(self, o)
def append(self, item):
i = item
if type(item) is str:
i = u_encode(item)
MultiPurposeList.append(self, i)
def insert(self, i, item):
_i = item
if type(_i) is str:
_i = u_encode(item)
MultiPurposeList.insert(self, i, _i)
def index(self, item, *args):
i = item
if type(i) is unicode:
i = u_encode(item)
MultiPurposeList.index(self, i, *args)
def pop(self, i=-1):
return u_decode(MultiPurposeList.pop(self, i))
def extend(self, other):
o = []
o_tmp = other
if type(o_tmp) not in (list, tuple) and not isinstance(o_tmp, UserList):
o_tmp = list(o_tmp)
for i in o_tmp:
if type(i) is unicode:
o.append(u_encode(i))
else:
o.append(i)
MultiPurposeList.extend(self, o)