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,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 -*-