├── ftplugin ├── orgmode │ ├── __init__.py │ ├── liborgmode │ │ ├── __init__.py │ │ ├── agenda.py │ │ ├── agendafilter.py │ │ ├── base.py │ │ ├── documents.py │ │ ├── orgdate.py │ │ ├── checkboxes.py │ │ └── dom_obj.py │ ├── plugins │ │ ├── __init__.py │ │ ├── LoggingWork.py │ │ ├── Export.py │ │ ├── ShowHide.py │ │ ├── TagsProperties.py │ │ ├── Hyperlinks.py │ │ ├── Misc.py │ │ ├── EditCheckbox.py │ │ ├── Agenda.py │ │ ├── Date.py │ │ ├── Navigator.py │ │ └── Todo.py │ ├── exceptions.py │ ├── settings.py │ ├── menu.py │ ├── keybinding.py │ ├── _vim.py │ └── vimbuffer.py ├── org.cnf └── org.vim ├── ftdetect └── org.vim ├── syntax ├── orgtodo.vim ├── orgagenda.vim └── org.vim ├── README └── indent └── org.vim /ftplugin/orgmode/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /ftplugin/orgmode/liborgmode/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /ftdetect/org.vim: -------------------------------------------------------------------------------- 1 | autocmd BufNewFile,BufRead *.org setfiletype org 2 | "autocmd BufNewFile,BufReadPost org:todo* setfiletype orgtodo 3 | -------------------------------------------------------------------------------- /ftplugin/org.cnf: -------------------------------------------------------------------------------- 1 | --langdef=org 2 | --langmap=org:.org 3 | --regex-org=/^(\*+)[[:space:]]+(.*)([[:space:]]+:[^\t ]*:)?$/\1 \2/s,sections/ 4 | --regex-org=/\[\[([^][]+)\]\]/\1/h,hyperlinks/ 5 | --regex-org=/\[\[[^][]+\]\[([^][]+)\]\]/\1/h,hyperlinks/ 6 | -------------------------------------------------------------------------------- /ftplugin/orgmode/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class PluginError(Exception): 5 | def __init__(self, message): 6 | Exception.__init__(self, message) 7 | 8 | 9 | class BufferNotFound(Exception): 10 | def __init__(self, message): 11 | Exception.__init__(self, message) 12 | 13 | 14 | class BufferNotInSync(Exception): 15 | def __init__(self, message): 16 | Exception.__init__(self, message) 17 | 18 | 19 | class HeadingDomError(Exception): 20 | def __init__(self, message): 21 | Exception.__init__(self, message) 22 | 23 | # vim: set noexpandtab: 24 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/LoggingWork.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | from orgmode._vim import echo, echom, echoe, ORGMODE, apply_count, repeat 6 | from orgmode.menu import Submenu, Separator, ActionEntry 7 | from orgmode.keybinding import Keybinding, Plug, Command 8 | 9 | 10 | class LoggingWork(object): 11 | u""" LoggingWork plugin """ 12 | 13 | def __init__(self): 14 | u""" Initialize plugin """ 15 | object.__init__(self) 16 | # menu entries this plugin should create 17 | self.menu = ORGMODE.orgmenu + Submenu(u'&Logging work') 18 | 19 | # key bindings for this plugin 20 | # key bindings are also registered through the menu so only additional 21 | # bindings should be put in this variable 22 | self.keybindings = [] 23 | 24 | # commands for this plugin 25 | self.commands = [] 26 | 27 | @classmethod 28 | def action(cls): 29 | u""" Some kind of action 30 | 31 | :returns: TODO 32 | """ 33 | pass 34 | 35 | def register(self): 36 | u""" 37 | Registration of plugin. Key bindings and other initialization should be done. 38 | """ 39 | # an Action menu entry which binds "keybinding" to action ":action" 40 | self.commands.append(Command(u'OrgLoggingRecordDoneTime', u':py ORGMODE.plugins[u"LoggingWork"].action()')) 41 | self.menu + ActionEntry(u'&Record DONE time', self.commands[-1]) 42 | -------------------------------------------------------------------------------- /syntax/orgtodo.vim: -------------------------------------------------------------------------------- 1 | syn match org_todo_key /\[\zs[^]]*\ze\]/ 2 | hi def link org_todo_key Identifier 3 | 4 | let s:todo_headings = '' 5 | let s:i = 1 6 | while s:i <= g:org_heading_highlight_levels 7 | if s:todo_headings == '' 8 | let s:todo_headings = 'containedin=org_heading' . s:i 9 | else 10 | let s:todo_headings = s:todo_headings . ',org_heading' . s:i 11 | endif 12 | let s:i += 1 13 | endwhile 14 | unlet! s:i 15 | 16 | if !exists('g:loaded_orgtodo_syntax') 17 | let g:loaded_orgtodo_syntax = 1 18 | function! s:ReadTodoKeywords(keywords, todo_headings) 19 | let l:default_group = 'Todo' 20 | for l:i in a:keywords 21 | if type(l:i) == 3 22 | call s:ReadTodoKeywords(l:i, a:todo_headings) 23 | continue 24 | endif 25 | if l:i == '|' 26 | let l:default_group = 'Question' 27 | continue 28 | endif 29 | " strip access key 30 | let l:_i = substitute(l:i, "\(.*$", "", "") 31 | 32 | let l:group = l:default_group 33 | for l:j in g:org_todo_keyword_faces 34 | if l:j[0] == l:_i 35 | let l:group = 'orgtodo_todo_keyword_face_' . l:_i 36 | call OrgExtendHighlightingGroup(l:default_group, l:group, OrgInterpretFaces(l:j[1])) 37 | break 38 | endif 39 | endfor 40 | exec 'syntax match orgtodo_todo_keyword_' . l:_i . ' /' . l:_i .'/ ' . a:todo_headings 41 | exec 'hi def link orgtodo_todo_keyword_' . l:_i . ' ' . l:group 42 | endfor 43 | endfunction 44 | endif 45 | 46 | call s:ReadTodoKeywords(g:org_todo_keywords, s:todo_headings) 47 | unlet! s:todo_headings 48 | -------------------------------------------------------------------------------- /ftplugin/orgmode/liborgmode/agenda.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | u""" 4 | Agenda 5 | ~~~~~~~~~~~~~~~~~~ 6 | 7 | The agenda is one of the main concepts of orgmode. It allows to 8 | collect TODO items from multiple org documents in an agenda view. 9 | 10 | Features: 11 | * filtering 12 | * sorting 13 | """ 14 | 15 | from orgmode.liborgmode.agendafilter import filter_items 16 | from orgmode.liborgmode.agendafilter import is_within_week_and_active_todo 17 | from orgmode.liborgmode.agendafilter import contains_active_todo 18 | from orgmode.liborgmode.agendafilter import contains_active_date 19 | 20 | 21 | class AgendaManager(object): 22 | u"""Simple parsing of Documents to create an agenda.""" 23 | 24 | def __init__(self): 25 | super(AgendaManager, self).__init__() 26 | 27 | def get_todo(self, documents): 28 | u""" 29 | Get the todo agenda for the given documents (list of document). 30 | """ 31 | filtered = [] 32 | for i, document in enumerate(documents): 33 | # filter and return headings 34 | tmp = filter_items(document.all_headings(), [contains_active_todo]) 35 | filtered.extend(tmp) 36 | return sorted(filtered) 37 | 38 | def get_next_week_and_active_todo(self, documents): 39 | u""" 40 | Get the agenda for next week for the given documents (list of 41 | document). 42 | """ 43 | filtered = [] 44 | for i, document in enumerate(documents): 45 | # filter and return headings 46 | tmp = filter_items( 47 | document.all_headings(), 48 | [is_within_week_and_active_todo]) 49 | filtered.extend(tmp) 50 | return sorted(filtered) 51 | 52 | def get_timestamped_items(self, documents): 53 | u""" 54 | Get all time-stamped items in a time-sorted way for the given 55 | documents (list of document). 56 | """ 57 | filtered = [] 58 | for i, document in enumerate(documents): 59 | # filter and return headings 60 | tmp = filter_items( 61 | document.all_headings(), 62 | [contains_active_date]) 63 | filtered.extend(tmp) 64 | return sorted(filtered) 65 | 66 | # vim: set noexpandtab: 67 | -------------------------------------------------------------------------------- /ftplugin/orgmode/liborgmode/agendafilter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | u""" 4 | agendafilter 5 | ~~~~~~~~~~~~~~~~ 6 | 7 | AgendaFilter contains all the filters that can be applied to create the 8 | agenda. 9 | 10 | 11 | All functions except filter_items() in the module are filters. Given a 12 | heading they return if the heading meets the critera of the filter. 13 | 14 | The function filter_items() can combine different filters and only returns 15 | the filtered headings. 16 | """ 17 | 18 | from datetime import datetime 19 | from datetime import timedelta 20 | 21 | 22 | def filter_items(headings, filters): 23 | u""" 24 | Filter the given headings. Return the list of headings which were not 25 | filtered. 26 | 27 | :headings: is an list of headings 28 | :filters: is the list of filters that are to be applied. all function in 29 | this module (except this function) are filters. 30 | 31 | You can use it like this: 32 | 33 | >>> filtered = filter_items(headings, [contains_active_date, 34 | contains_active_todo]) 35 | 36 | """ 37 | filtered = headings 38 | for f in filters: 39 | filtered = filter(f, filtered) 40 | return filtered 41 | 42 | 43 | def is_within_week(heading): 44 | u""" 45 | Return True if the date in the deading is within a week in the future (or 46 | older. 47 | """ 48 | if contains_active_date(heading): 49 | next_week = datetime.today() + timedelta(days=7) 50 | if heading.active_date < next_week: 51 | return True 52 | 53 | 54 | def is_within_week_and_active_todo(heading): 55 | u""" 56 | Return True if heading contains an active TODO and the date is within a 57 | week. 58 | """ 59 | return is_within_week(heading) and contains_active_todo(heading) 60 | 61 | 62 | def contains_active_todo(heading): 63 | u""" 64 | Return True if heading contains an active TODO. 65 | 66 | FIXME: the todo checking should consider a number of different active todo 67 | states 68 | """ 69 | return heading.todo == u"TODO" 70 | 71 | 72 | def contains_active_date(heading): 73 | u""" 74 | Return True if heading contains an active date. 75 | """ 76 | return not(heading.active_date is None) 77 | 78 | # vim: set noexpandtab: 79 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is a mirror of http://www.vim.org/scripts/script.php?script_id=3642 2 | 3 | INTRODUCTION *vim-orgmode* *orgguide-introduction* 4 | 5 | Vim-orgmode: Text outlining and task management for Vim based on Emacs' 6 | Org-Mode. 7 | 8 | The idea for this plugin was born by listening to the Floss Weekly podcast 9 | introducing Emacs' Org-Mode (http://twit.tv/floss136). Org-Mode has a lot of 10 | strong features like folding, views (sparse tree) and scheduling of tasks. 11 | These are completed by hyperlinks, tags, todo states, priorities aso. 12 | 13 | Vim-orgmode aims at providing the same functionality for Vim and for command 14 | line tools*. 15 | 16 | * for command line tools and other programs a library liborgmode is provided. 17 | It encapsulates all functionality for parsing and modifying org files. 18 | 19 | ------------------------------------------------------------------------------ 20 | Preface~ 21 | vim-orgmode is a file type plugin for keeping notes, maintaining TODO 22 | lists, and doing project planning with a fast and effective plain-text 23 | system. It is also an authoring and publishing system. 24 | 25 | This document is a copy of the orgmode-guide for emacs 26 | (http://orgmode.org/) with modifications for vim. It contains all basic 27 | features and commands, along with important hints for customization. 28 | 29 | ------------------------------------------------------------------------------ 30 | Features~ 31 | vim-orgmode is still very young but already quite usable. Here is a short 32 | list of the already supported features: 33 | 34 | - Cycle visibility of headings 35 | - Navigate between headings 36 | - Edit the structure of the document: add, move, promote, denote headings 37 | and more 38 | - Hyperlinks within vim-orgmode and outside (files, webpages, etc.) 39 | - TODO list management 40 | - Tags for headings 41 | - Basic date handling 42 | - Export (via emacs) 43 | 44 | More features are coming... 45 | 46 | Feedback~ 47 | If you find problems with vim-orgmode, or if you have questions, remarks, or 48 | ideas about it, please create a ticket on 49 | https://github.com/jceb/vim-orgmode 50 | -------------------------------------------------------------------------------- /syntax/orgagenda.vim: -------------------------------------------------------------------------------- 1 | " TODO do we really need a separate syntax file for the agenda? 2 | " - Most of the stuff here is also in syntax.org 3 | " - DRY! 4 | 5 | syn match org_todo_key /\[\zs[^]]*\ze\]/ 6 | hi def link org_todo_key Identifier 7 | 8 | let s:todo_headings = '' 9 | let s:i = 1 10 | while s:i <= g:org_heading_highlight_levels 11 | if s:todo_headings == '' 12 | let s:todo_headings = 'containedin=org_heading' . s:i 13 | else 14 | let s:todo_headings = s:todo_headings . ',org_heading' . s:i 15 | endif 16 | let s:i += 1 17 | endwhile 18 | unlet! s:i 19 | 20 | if !exists('g:loaded_orgagenda_syntax') 21 | let g:loaded_orgagenda_syntax = 1 22 | function! s:ReadTodoKeywords(keywords, todo_headings) 23 | let l:default_group = 'Todo' 24 | for l:i in a:keywords 25 | if type(l:i) == 3 26 | call s:ReadTodoKeywords(l:i, a:todo_headings) 27 | continue 28 | endif 29 | if l:i == '|' 30 | let l:default_group = 'Question' 31 | continue 32 | endif 33 | " strip access key 34 | let l:_i = substitute(l:i, "\(.*$", "", "") 35 | 36 | let l:group = l:default_group 37 | for l:j in g:org_todo_keyword_faces 38 | if l:j[0] == l:_i 39 | let l:group = 'orgtodo_todo_keyword_face_' . l:_i 40 | call OrgExtendHighlightingGroup(l:default_group, l:group, OrgInterpretFaces(l:j[1])) 41 | break 42 | endif 43 | endfor 44 | exec 'syntax match orgtodo_todo_keyword_' . l:_i . ' /' . l:_i .'/ ' . a:todo_headings 45 | exec 'hi def link orgtodo_todo_keyword_' . l:_i . ' ' . l:group 46 | endfor 47 | endfunction 48 | endif 49 | 50 | call s:ReadTodoKeywords(g:org_todo_keywords, s:todo_headings) 51 | unlet! s:todo_headings 52 | 53 | " Timestamps 54 | "<2003-09-16 Tue> 55 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a>\)/ 56 | "<2003-09-16 Tue 12:00> 57 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d>\)/ 58 | "<2003-09-16 Tue 12:00-12:30> 59 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d-\d\d:\d\d>\)/ 60 | "<2003-09-16 Tue>--<2003-09-16 Tue> 61 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a>--<\d\d\d\d-\d\d-\d\d \a\a\a>\)/ 62 | "<2003-09-16 Tue 12:00>--<2003-09-16 Tue 12:00> 63 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d>--<\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d>\)/ 64 | syn match org_timestamp /\(<%%(diary-float.\+>\)/ 65 | hi def link org_timestamp PreProc 66 | 67 | " special words 68 | syn match today /TODAY$/ 69 | hi def link today PreProc 70 | 71 | syn match week_agenda /^Week Agenda:$/ 72 | hi def link week_agenda PreProc 73 | 74 | " Hyperlinks 75 | syntax match hyperlink "\[\{2}[^][]*\(\]\[[^][]*\)\?\]\{2}" contains=hyperlinkBracketsLeft,hyperlinkURL,hyperlinkBracketsRight containedin=ALL 76 | syntax match hyperlinkBracketsLeft contained "\[\{2}" conceal 77 | syntax match hyperlinkURL contained "[^][]*\]\[" conceal 78 | syntax match hyperlinkBracketsRight contained "\]\{2}" conceal 79 | hi def link hyperlink Underlined 80 | -------------------------------------------------------------------------------- /ftplugin/orgmode/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | SCOPE_ALL = 1 6 | 7 | # for all vim-orgmode buffers 8 | SCOPE_GLOBAL = 2 9 | 10 | # just for the current buffer - has priority before the global settings 11 | SCOPE_BUFFER = 4 12 | 13 | VARIABLE_LEADER = {SCOPE_GLOBAL: u'g', SCOPE_BUFFER: u'b'} 14 | 15 | u""" Evaluate and store settings """ 16 | 17 | 18 | def get(setting, default=None, scope=SCOPE_ALL): 19 | u""" Evaluate setting in scope of the current buffer, 20 | globally and also from the contents of the current buffer 21 | 22 | WARNING: Only string values are converted to unicode. If a different value 23 | is received, e.g. a list or dict, no conversion is done. 24 | 25 | :setting: name of the variable to evaluate 26 | :default: default value in case the variable is empty 27 | 28 | :returns: variable value 29 | """ 30 | # TODO first read setting from org file which take precedence over vim 31 | # variable settings 32 | if (scope & SCOPE_ALL | SCOPE_BUFFER) and \ 33 | int(vim.eval((u'exists("b:%s")' % setting).encode(u'utf-8'))): 34 | res = vim.eval((u"b:%s" % setting).encode(u'utf-8')) 35 | if type(res) in (unicode, str): 36 | return res.decode(u'utf-8') 37 | return res 38 | 39 | elif (scope & SCOPE_ALL | SCOPE_GLOBAL) and \ 40 | int(vim.eval((u'exists("g:%s")' % setting).encode(u'utf-8'))): 41 | res = vim.eval((u"g:%s" % setting).encode(u'utf-8')) 42 | if type(res) in (unicode, str): 43 | return res.decode(u'utf-8') 44 | return res 45 | return default 46 | 47 | 48 | def set(setting, value, scope=SCOPE_GLOBAL, overwrite=False): 49 | u""" Store setting in the definied scope 50 | 51 | WARNING: For the return value, only string are converted to unicode. If a 52 | different value is received by vim.eval, e.g. a list or dict, no conversion 53 | is done. 54 | 55 | :setting: name of the setting 56 | :value: the actual value, repr is called on the value to create a string 57 | representation 58 | :scope: the scope o the setting/variable 59 | :overwrite: overwrite existing settings (probably user definied settings) 60 | 61 | :returns: the new value in case of overwrite==False the current value 62 | """ 63 | if (not overwrite) and ( 64 | int(vim.eval((u'exists("%s:%s")' % \ 65 | (VARIABLE_LEADER[scope], setting)).encode(u'utf-8')))): 66 | res = vim.eval( 67 | (u'%s:%s' % (VARIABLE_LEADER[scope], setting)).encode(u'utf-8')) 68 | if type(res) in (unicode, str): 69 | return res.decode(u'utf-8') 70 | return res 71 | v = repr(value) 72 | if type(value) == unicode: 73 | # strip leading u of unicode string representations 74 | v = v[1:] 75 | 76 | cmd = u'let %s:%s = %s' % (VARIABLE_LEADER[scope], setting, v) 77 | vim.command(cmd.encode(u'utf-8')) 78 | return value 79 | 80 | 81 | def unset(setting, scope=SCOPE_GLOBAL): 82 | u""" Unset setting int the definied scope 83 | :setting: name of the setting 84 | :scope: the scope o the setting/variable 85 | 86 | :returns: last value of setting 87 | """ 88 | value = get(setting, scope=scope) 89 | cmd = u'unlet! %s:%s' % (VARIABLE_LEADER[scope], setting) 90 | vim.command(cmd.encode(u'utf-8')) 91 | return value 92 | 93 | 94 | # vim: set noexpandtab: 95 | -------------------------------------------------------------------------------- /indent/org.vim: -------------------------------------------------------------------------------- 1 | " Delete the next line to avoid the special indention of items 2 | if !exists("g:org_indent") 3 | let g:org_indent = 1 4 | endif 5 | 6 | setlocal foldtext=GetOrgFoldtext() 7 | setlocal fillchars-=fold:- 8 | setlocal fillchars+=fold:\ 9 | setlocal foldexpr=GetOrgFolding() 10 | setlocal foldmethod=expr 11 | setlocal indentexpr=GetOrgIndent() 12 | setlocal nolisp 13 | setlocal nosmartindent 14 | setlocal autoindent 15 | 16 | function! GetOrgIndent() 17 | if g:org_indent == 0 18 | return -1 19 | endif 20 | 21 | python << EOF 22 | from orgmode._vim import indent_orgmode 23 | indent_orgmode() 24 | EOF 25 | if exists('b:indent_level') 26 | let l:tmp = b:indent_level 27 | unlet b:indent_level 28 | return l:tmp 29 | else 30 | return -1 31 | endif 32 | endfunction 33 | 34 | function! GetOrgFolding() 35 | let l:mode = mode() 36 | if l:mode == 'i' 37 | " the cache size is limited to 3, because vim queries the current and 38 | " both surrounding lines when the user is typing in insert mode. The 39 | " cache is shared between GetOrgFolding and GetOrgFoldtext 40 | if ! exists('b:org_folding_cache') 41 | let b:org_folding_cache = {} 42 | endif 43 | 44 | if has_key(b:org_folding_cache, v:lnum) 45 | if match(b:org_folding_cache[v:lnum], '^>') == 0 && 46 | \ match(getline(v:lnum), '^\*\+\s') != 0 47 | " when the user pastes text or presses enter, it happens that 48 | " the cache starts to confuse vim's folding abilities 49 | " these entries can safely be removed 50 | unlet b:org_folding_cache[v:lnum] 51 | 52 | " the fold text cache is probably also damaged, delete it as 53 | " well 54 | unlet! b:org_foldtext_cache 55 | else 56 | return b:org_folding_cache[v:lnum] 57 | endif 58 | endif 59 | python << EOF 60 | from orgmode._vim import fold_orgmode 61 | fold_orgmode(allow_dirty=True) 62 | EOF 63 | else 64 | python << EOF 65 | from orgmode._vim import fold_orgmode 66 | fold_orgmode() 67 | EOF 68 | endif 69 | 70 | if exists('b:fold_expr') 71 | let l:tmp = b:fold_expr 72 | unlet b:fold_expr 73 | if l:mode == 'i' 74 | if ! has_key(b:org_folding_cache, v:lnum) 75 | if len(b:org_folding_cache) > 3 76 | let b:org_folding_cache = {} 77 | endif 78 | let b:org_folding_cache[v:lnum] = l:tmp 79 | endif 80 | endif 81 | return l:tmp 82 | else 83 | return -1 84 | endif 85 | endfunction 86 | 87 | function! SetOrgFoldtext(text) 88 | let b:foldtext = a:text 89 | endfunction 90 | 91 | function! GetOrgFoldtext() 92 | let l:mode = mode() 93 | if l:mode == 'i' 94 | " add a separate cache for fold text 95 | if ! exists('b:org_foldtext_cache') || 96 | \ ! has_key(b:org_foldtext_cache, 'timestamp') || 97 | \ b:org_foldtext_cache['timestamp'] > (localtime() + 10) 98 | let b:org_foldtext_cache = {'timestamp': localtime()} 99 | endif 100 | 101 | if has_key(b:org_foldtext_cache, v:foldstart) 102 | return b:org_foldtext_cache[v:foldstart] 103 | endif 104 | python << EOF 105 | from orgmode._vim import fold_text 106 | fold_text(allow_dirty=True) 107 | EOF 108 | else 109 | unlet! b:org_foldtext_cache 110 | python << EOF 111 | from orgmode._vim import fold_text 112 | fold_text() 113 | EOF 114 | endif 115 | 116 | if exists('b:foldtext') 117 | let l:tmp = b:foldtext 118 | unlet b:foldtext 119 | if l:mode == 'i' 120 | let b:org_foldtext_cache[v:foldstart] = l:tmp 121 | endif 122 | return l:tmp 123 | endif 124 | endfunction 125 | -------------------------------------------------------------------------------- /ftplugin/orgmode/liborgmode/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | base 5 | ~~~~~~~~~~ 6 | 7 | Here are some really basic data structures that are used throughout 8 | the liborgmode. 9 | """ 10 | 11 | from UserList import UserList 12 | 13 | 14 | def flatten_list(l): 15 | """TODO""" 16 | res = [] 17 | if type(l) in (tuple, list) or isinstance(l, UserList): 18 | for i in l: 19 | if type(i) in (list, tuple) or isinstance(i, UserList): 20 | res.extend(flatten_list(i)) 21 | else: 22 | res.append(i) 23 | return res 24 | 25 | 26 | class Direction(): 27 | u""" 28 | Direction is used to indicate the direction of certain actions. 29 | 30 | Example: it defines the direction headings get parted in. 31 | """ 32 | FORWARD = 1 33 | BACKWARD = 2 34 | 35 | 36 | class MultiPurposeList(UserList): 37 | u""" 38 | A Multi Purpose List is a list that calls a user defined hook on 39 | change. The implementation is very basic - the hook is called without any 40 | parameters. Otherwise the Multi Purpose List can be used like any other 41 | list. 42 | 43 | The member element "data" can be used to fill the list without causing the 44 | list to be marked dirty. This should only be used during initialization! 45 | """ 46 | 47 | def __init__(self, initlist=None, on_change=None): 48 | UserList.__init__(self, initlist) 49 | self._on_change = on_change 50 | 51 | def _changed(self): 52 | u""" 53 | Call hook 54 | """ 55 | if callable(self._on_change): 56 | self._on_change() 57 | 58 | def __setitem__(self, i, item): 59 | UserList.__setitem__(self, i, item) 60 | self._changed() 61 | 62 | def __delitem__(self, i): 63 | UserList.__delitem__(self, i) 64 | self._changed() 65 | 66 | def __setslice__(self, i, j, other): 67 | UserList.__setslice__(self, i, j, other) 68 | self._changed() 69 | 70 | def __delslice__(self, i, j): 71 | UserList.__delslice__(self, i, j) 72 | self._changed() 73 | 74 | def __getslice__(self, i, j): 75 | # fix UserList - don't return a new list of the same type but just the 76 | # normal list item 77 | i = max(i, 0) 78 | j = max(j, 0) 79 | return self.data[i:j] 80 | 81 | def __iadd__(self, other): 82 | res = UserList.__iadd__(self, other) 83 | self._changed() 84 | return res 85 | 86 | def __imul__(self, n): 87 | res = UserList.__imul__(self, n) 88 | self._changed() 89 | return res 90 | 91 | def append(self, item): 92 | UserList.append(self, item) 93 | self._changed() 94 | 95 | def insert(self, i, item): 96 | UserList.insert(self, i, item) 97 | self._changed() 98 | 99 | def pop(self, i=-1): 100 | item = self[i] 101 | del self[i] 102 | return item 103 | 104 | def remove(self, item): 105 | self.__delitem__(self.index(item)) 106 | 107 | def reverse(self): 108 | UserList.reverse(self) 109 | self._changed() 110 | 111 | def sort(self, *args, **kwds): 112 | UserList.sort(self, *args, **kwds) 113 | self._changed() 114 | 115 | def extend(self, other): 116 | UserList.extend(self, other) 117 | self._changed() 118 | 119 | 120 | def get_domobj_range(content=[], position=0, direction=Direction.FORWARD, identify_fun=None): 121 | u""" 122 | Get the start and end line number of the dom obj lines from content. 123 | 124 | :content: String to be recognized dom obj 125 | :positon: Line number in content 126 | :direction: Search direction 127 | :identify_fun: A identify function to recognize dom obj(Heading, Checkbox) title string. 128 | 129 | :return: Start and end line number for the recognized dom obj. 130 | """ 131 | len_cb = len(content) 132 | 133 | if position < 0 or position > len_cb: 134 | return (None, None) 135 | 136 | tmp_line = position 137 | start = None 138 | end = None 139 | 140 | if direction == Direction.FORWARD: 141 | while tmp_line < len_cb: 142 | if identify_fun(content[tmp_line]) is not None: 143 | if start is None: 144 | start = tmp_line 145 | elif end is None: 146 | end = tmp_line - 1 147 | if start is not None and end is not None: 148 | break 149 | tmp_line += 1 150 | else: 151 | while tmp_line >= 0 and tmp_line < len_cb: 152 | if identify_fun(content[tmp_line]) is not None: 153 | if start is None: 154 | start = tmp_line 155 | elif end is None: 156 | end = tmp_line - 1 157 | if start is not None and end is not None: 158 | break 159 | tmp_line -= 1 if start is None else -1 160 | 161 | return (start, end) 162 | 163 | # vim: set noexpandtab: 164 | -------------------------------------------------------------------------------- /ftplugin/org.vim: -------------------------------------------------------------------------------- 1 | " org.vim -- Text outlining and task management for Vim based on Emacs' Org-Mode 2 | " @Author : Jan Christoph Ebersbach (jceb@e-jc.de) 3 | " @License : AGPL3 (see http://www.gnu.org/licenses/agpl.txt) 4 | " @Created : 2010-10-03 5 | " @Last Modified: Tue 13. Sep 2011 20:52:57 +0200 CEST 6 | " @Revision : 0.4 7 | " vi: ft=vim:tw=80:sw=4:ts=4:fdm=marker 8 | 9 | if ! has('python') || v:version < 703 10 | echoerr "Unable to start orgmode. Orgmode depends on Vim >= 7.3 with Python support complied in." 11 | finish 12 | endif 13 | 14 | if ! exists('b:did_ftplugin') 15 | " default emacs settings 16 | setlocal comments=fb:*,b:#,fb:- 17 | setlocal commentstring=#\ %s 18 | setlocal conceallevel=2 concealcursor="nc" 19 | " original emacs settings are: setlocal tabstop=6 shiftwidth=6, but because 20 | " of checkbox indentation the following settings are used: 21 | setlocal tabstop=6 shiftwidth=6 22 | if exists('g:org_tag_column') 23 | exe 'setlocal textwidth='.g:org_tag_column 24 | else 25 | setlocal textwidth=77 26 | endif 27 | 28 | " expand tab for counting level of checkbox 29 | setlocal expandtab 30 | 31 | " register keybindings if they don't have been registered before 32 | if exists("g:loaded_org") 33 | python ORGMODE.register_keybindings() 34 | endif 35 | endif 36 | 37 | " Load orgmode just once {{{1 38 | if &cp || exists("g:loaded_org") 39 | finish 40 | endif 41 | let g:loaded_org = 1 42 | 43 | " Default org plugins that will be loaded (in the given order) 44 | if ! exists('g:org_plugins') && ! exists('b:org_plugins') 45 | let g:org_plugins = ['ShowHide', '|', 'Navigator', 'EditStructure', 'EditCheckbox', '|', 'Hyperlinks', '|', 'Todo', 'TagsProperties', 'Date', 'Agenda', 'Misc', '|', 'Export'] 46 | endif 47 | 48 | if ! exists('g:org_syntax_highlight_leading_stars') && ! exists('b:org_syntax_highlight_leading_stars') 49 | let g:org_syntax_highlight_leading_stars = 1 50 | endif 51 | 52 | " Menu and document handling {{{1 53 | function! OrgRegisterMenu() 54 | python ORGMODE.register_menu() 55 | endfunction 56 | 57 | function! OrgUnregisterMenu() 58 | python ORGMODE.unregister_menu() 59 | endfunction 60 | 61 | function! OrgDeleteUnusedDocument(bufnr) 62 | python << EOF 63 | b = int(vim.eval('a:bufnr')) 64 | if b in ORGMODE._documents: 65 | del ORGMODE._documents[b] 66 | EOF 67 | endfunction 68 | 69 | " show and hide Org menu depending on the filetype 70 | augroup orgmode 71 | au BufEnter * :if &filetype == "org" | call OrgRegisterMenu() | endif 72 | au BufLeave * :if &filetype == "org" | call OrgUnregisterMenu() | endif 73 | au BufDelete * :call OrgDeleteUnusedDocument(expand('')) 74 | augroup END 75 | 76 | " Start orgmode {{{1 77 | " Expand our path 78 | python << EOF 79 | import vim, os, sys 80 | 81 | for p in vim.eval("&runtimepath").split(','): 82 | dname = os.path.join(p, "ftplugin") 83 | if os.path.exists(os.path.join(dname, "orgmode")): 84 | if dname not in sys.path: 85 | sys.path.append(dname) 86 | break 87 | 88 | from orgmode._vim import ORGMODE, insert_at_cursor, get_user_input, date_to_str 89 | ORGMODE.start() 90 | 91 | from Date import Date 92 | import datetime 93 | EOF 94 | 95 | " 3rd Party Plugin Integration {{{1 96 | " * Repeat {{{2 97 | try 98 | call repeat#set() 99 | catch 100 | endtry 101 | 102 | " * Tagbar {{{2 103 | let g:tagbar_type_org = { 104 | \ 'ctagstype' : 'org', 105 | \ 'kinds' : [ 106 | \ 's:sections', 107 | \ 'h:hyperlinks', 108 | \ ], 109 | \ 'sort' : 0, 110 | \ 'deffile' : expand(':p:h') . '/org.cnf' 111 | \ } 112 | 113 | " * Taglist {{{2 114 | if exists('g:Tlist_Ctags_Cmd') 115 | " Pass parameters to taglist 116 | let g:tlist_org_settings = 'org;s:section;h:hyperlinks' 117 | let g:Tlist_Ctags_Cmd .= ' --options=' . expand(':p:h') . '/org.cnf ' 118 | endif 119 | 120 | " * Calendar.vim {{{2 121 | fun CalendarAction(day, month, year, week, dir) 122 | let g:org_timestamp = printf("%04d-%02d-%02d Fri", a:year, a:month, a:day) 123 | let datetime_date = printf("datetime.date(%d, %d, %d)", a:year, a:month, a:day) 124 | exe "py selected_date = " . datetime_date 125 | " get_user_input 126 | let msg = printf("Inserting %s | Modify date", g:org_timestamp) 127 | exe "py modifier = get_user_input('" . msg . "')" 128 | " change date according to user input 129 | exe "py print modifier" 130 | exe "py newdate = Date._modify_time(selected_date, modifier)" 131 | exe "py newdate = date_to_str(newdate)" 132 | " close Calendar 133 | exe "q" 134 | " goto previous window 135 | exe "wincmd p" 136 | exe "py timestamp = '" . g:org_timestamp_template . "' % newdate" 137 | exe "py insert_at_cursor(timestamp)" 138 | " restore calendar_action 139 | let g:calendar_action = g:org_calendar_action_backup 140 | endf 141 | -------------------------------------------------------------------------------- /ftplugin/orgmode/menu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | from orgmode.keybinding import Command, Plug, Keybinding 6 | from orgmode.keybinding import MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT 7 | 8 | def register_menu(f): 9 | def r(*args, **kwargs): 10 | p = f(*args, **kwargs) 11 | def create(entry): 12 | if isinstance(entry, Submenu) or isinstance(entry, Separator) \ 13 | or isinstance(entry, ActionEntry): 14 | entry.create() 15 | 16 | if hasattr(p, u'menu'): 17 | if isinstance(p.menu, list) or isinstance(p.menu, tuple): 18 | for e in p.menu: 19 | create(e) 20 | else: 21 | create(p.menu) 22 | return p 23 | return r 24 | 25 | 26 | def add_cmd_mapping_menu(plugin, name, function, key_mapping, menu_desrc): 27 | u"""A helper function to create a vim command and keybinding and add these 28 | to the menu for a given plugin. 29 | 30 | :plugin: the plugin to operate on. 31 | :name: the name of the vim command (and the name of the Plug) 32 | :function: the actual python function which is called when executing the 33 | vim command. 34 | :key_mapping: the keymapping to execute the command. 35 | :menu_desrc: the text which appears in the menu. 36 | """ 37 | cmd = Command(name, function) 38 | keybinding = Keybinding(key_mapping, Plug(name, cmd)) 39 | 40 | plugin.commands.append(cmd) 41 | plugin.keybindings.append(keybinding) 42 | plugin.menu + ActionEntry(menu_desrc, keybinding) 43 | 44 | 45 | class Submenu(object): 46 | u""" Submenu entry """ 47 | 48 | def __init__(self, name, parent=None): 49 | object.__init__(self) 50 | self.name = name 51 | self.parent = parent 52 | self._children = [] 53 | 54 | def __add__(self, entry): 55 | if entry not in self._children: 56 | self._children.append(entry) 57 | entry.parent = self 58 | return entry 59 | 60 | def __sub__(self, entry): 61 | if entry in self._children: 62 | idx = self._children.index(entry) 63 | del self._children[idx] 64 | 65 | @property 66 | def children(self): 67 | return self._children[:] 68 | 69 | def get_menu(self): 70 | n = self.name.replace(u' ', u'\\ ') 71 | if self.parent: 72 | return u'%s.%s' % (self.parent.get_menu(), n) 73 | return n 74 | 75 | def create(self): 76 | for c in self.children: 77 | c.create() 78 | 79 | def __str__(self): 80 | res = self.name 81 | for c in self.children: 82 | res += str(c) 83 | return res 84 | 85 | class Separator(object): 86 | u""" Menu entry for a Separator """ 87 | 88 | def __init__(self, parent=None): 89 | object.__init__(self) 90 | self.parent = parent 91 | 92 | def __unicode__(self): 93 | return u'-----' 94 | 95 | def __str__(self): 96 | return self.__unicode__().encode(u'utf-8') 97 | 98 | def create(self): 99 | if self.parent: 100 | menu = self.parent.get_menu() 101 | vim.command((u'menu %s.-%s- :' % (menu, id(self))).encode(u'utf-8')) 102 | 103 | class ActionEntry(object): 104 | u""" ActionEntry entry """ 105 | 106 | def __init__(self, lname, action, rname=None, mode=MODE_NORMAL, parent=None): 107 | u""" 108 | :lname: menu title on the left hand side of the menu entry 109 | :action: could be a vim command sequence or an actual Keybinding 110 | :rname: menu title that appears on the right hand side of the menu 111 | entry. If action is a Keybinding this value ignored and is 112 | taken from the Keybinding 113 | :mode: defines when the menu entry/action is executable 114 | :parent: the parent instance of this object. The only valid parent is Submenu 115 | """ 116 | object.__init__(self) 117 | self._lname = lname 118 | self._action = action 119 | self._rname = rname 120 | if mode not in (MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT): 121 | raise ValueError(u'Parameter mode not in MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT') 122 | self._mode = mode 123 | self.parent = parent 124 | 125 | def __str__(self): 126 | return u'%s\t%s' % (self.lname, self.rname) 127 | 128 | @property 129 | def lname(self): 130 | return self._lname.replace(u' ', u'\\ ') 131 | 132 | @property 133 | def action(self): 134 | if isinstance(self._action, Keybinding): 135 | return self._action.action 136 | return self._action 137 | 138 | @property 139 | def rname(self): 140 | if isinstance(self._action, Keybinding): 141 | return self._action.key.replace(u'', u'Tab') 142 | return self._rname 143 | 144 | @property 145 | def mode(self): 146 | if isinstance(self._action, Keybinding): 147 | return self._action.mode 148 | return self._mode 149 | 150 | def create(self): 151 | menucmd = u':%smenu ' % self.mode 152 | menu = u'' 153 | cmd = u'' 154 | 155 | if self.parent: 156 | menu = self.parent.get_menu() 157 | menu += u'.%s' % self.lname 158 | 159 | if self.rname: 160 | cmd = u'%s %s%s %s' % (menucmd, menu, self.rname, self.action) 161 | else: 162 | cmd = u'%s %s %s' % (menucmd, menu, self.action) 163 | 164 | vim.command(cmd.encode(u'utf-8')) 165 | 166 | # keybindings should be stored in the plugin.keybindings property and be registered by the appropriate keybinding registrar 167 | #if isinstance(self._action, Keybinding): 168 | # self._action.create() 169 | 170 | 171 | # vim: set noexpandtab: 172 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/Export.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import subprocess 5 | 6 | import vim 7 | 8 | from orgmode._vim import ORGMODE, echoe, echom 9 | from orgmode.menu import Submenu, ActionEntry, add_cmd_mapping_menu 10 | from orgmode.keybinding import Keybinding, Plug, Command 11 | from orgmode import settings 12 | 13 | 14 | class Export(object): 15 | u""" 16 | Export a orgmode file using emacs orgmode. 17 | 18 | This is a *very simple* wrapper of the emacs/orgmode export. emacs and 19 | orgmode need to be installed. We simply call emacs with some options to 20 | export the .org. 21 | 22 | TODO: Offer export options in vim. Don't use the menu. 23 | TODO: Maybe use a native implementation. 24 | """ 25 | 26 | def __init__(self): 27 | u""" Initialize plugin """ 28 | object.__init__(self) 29 | # menu entries this plugin should create 30 | self.menu = ORGMODE.orgmenu + Submenu(u'Export') 31 | 32 | # key bindings for this plugin 33 | # key bindings are also registered through the menu so only additional 34 | # bindings should be put in this variable 35 | self.keybindings = [] 36 | 37 | # commands for this plugin 38 | self.commands = [] 39 | 40 | @classmethod 41 | def _get_init_script(cls): 42 | init_script = settings.get(u'org_export_init_script', u'') 43 | if init_script: 44 | init_script = os.path.expandvars(os.path.expanduser(init_script)) 45 | if os.path.exists(init_script): 46 | return init_script 47 | else: 48 | echoe(u'Unable to find init script %s' % init_script) 49 | 50 | @classmethod 51 | def _export(cls, format_): 52 | """Export current file to format_. 53 | 54 | :format_: pdf or html 55 | :returns: return code 56 | """ 57 | emacsbin = os.path.expandvars(os.path.expanduser( 58 | settings.get(u'org_export_emacs', u'/usr/bin/emacs'))) 59 | if not os.path.exists(emacsbin): 60 | echoe(u'Unable to find emacs binary %s' % emacsbin) 61 | 62 | # build the export command 63 | cmd = [ 64 | emacsbin, 65 | u'-nw', 66 | u'--batch', 67 | u'--visit=%s' % vim.eval(u'expand("%:p")'), 68 | u'--funcall=%s' % format_ 69 | ] 70 | # source init script as well 71 | init_script = cls._get_init_script() 72 | if init_script: 73 | cmd.extend(['--script', init_script]) 74 | 75 | # export 76 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 77 | p.wait() 78 | 79 | if p.returncode != 0 or settings.get(u'org_export_verbose') == 1: 80 | echom('\n'.join(p.communicate())) 81 | return p.returncode 82 | 83 | @classmethod 84 | def topdf(cls): 85 | u"""Export the current buffer as pdf using emacs orgmode.""" 86 | ret = cls._export(u'org-latex-export-to-pdf') 87 | if ret != 0: 88 | echoe(u'PDF export failed.') 89 | else: 90 | echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'pdf')) 91 | 92 | @classmethod 93 | def tohtml(cls): 94 | u"""Export the current buffer as html using emacs orgmode.""" 95 | ret = cls._export(u'org-html-export-to-html') 96 | if ret != 0: 97 | echoe(u'HTML export failed.') 98 | else: 99 | echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'html')) 100 | 101 | @classmethod 102 | def tolatex(cls): 103 | u"""Export the current buffer as latex using emacs orgmode.""" 104 | ret = cls._export(u'org-latex-export-to-latex') 105 | if ret != 0: 106 | echoe(u'latex export failed.') 107 | else: 108 | echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'tex')) 109 | 110 | @classmethod 111 | def tomarkdown(cls): 112 | u"""Export the current buffer as markdown using emacs orgmode.""" 113 | ret = cls._export(u'org-md-export-to-markdown') 114 | if ret != 0: 115 | echoe('Markdown export failed. Make sure org-md-export-to-markdown is loaded in emacs, see the manual for details.') 116 | else: 117 | echom(u'Export successful: %s.%s' % (vim.eval(u'expand("%:r")'), 'md')) 118 | 119 | def register(self): 120 | u"""Registration and keybindings.""" 121 | 122 | # path to emacs executable 123 | settings.set(u'org_export_emacs', u'/usr/bin/emacs') 124 | # verbose output for export 125 | settings.set(u'org_export_verbose', 0) 126 | # allow the user to define an initialization script 127 | settings.set(u'org_export_init_script', u'') 128 | 129 | # to PDF 130 | add_cmd_mapping_menu( 131 | self, 132 | name=u'OrgExportToPDF', 133 | function=u':py ORGMODE.plugins[u"Export"].topdf()', 134 | key_mapping=u'ep', 135 | menu_desrc=u'To PDF (via Emacs)' 136 | ) 137 | # to latex 138 | add_cmd_mapping_menu( 139 | self, 140 | name=u'OrgExportToLaTeX', 141 | function=u':py ORGMODE.plugins[u"Export"].tolatex()', 142 | key_mapping=u'el', 143 | menu_desrc=u'To LaTeX (via Emacs)' 144 | ) 145 | # to HTML 146 | add_cmd_mapping_menu( 147 | self, 148 | name=u'OrgExportToHTML', 149 | function=u':py ORGMODE.plugins[u"Export"].tohtml()', 150 | key_mapping=u'eh', 151 | menu_desrc=u'To HTML (via Emacs)' 152 | ) 153 | # to Markdown 154 | add_cmd_mapping_menu( 155 | self, 156 | name=u'OrgExportToMarkdown', 157 | function=u':py ORGMODE.plugins[u"Export"].tomarkdown()', 158 | key_mapping=u'em', 159 | menu_desrc=u'To Markdown (via Emacs)' 160 | ) 161 | 162 | # vim: set noexpandtab: 163 | -------------------------------------------------------------------------------- /ftplugin/orgmode/keybinding.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | MODE_ALL = u'a' 6 | MODE_NORMAL = u'n' 7 | MODE_VISUAL = u'v' 8 | MODE_INSERT = u'i' 9 | MODE_OPERATOR = u'o' 10 | 11 | OPTION_BUFFER_ONLY = u'' 12 | OPTION_SLIENT = u'' 13 | 14 | 15 | def _register(f, name): 16 | def r(*args, **kwargs): 17 | p = f(*args, **kwargs) 18 | if hasattr(p, name) and isinstance(getattr(p, name), list): 19 | for i in getattr(p, name): 20 | i.create() 21 | return p 22 | return r 23 | 24 | 25 | def register_keybindings(f): 26 | return _register(f, u'keybindings') 27 | 28 | 29 | def register_commands(f): 30 | return _register(f, u'commands') 31 | 32 | 33 | class Command(object): 34 | u""" A vim command """ 35 | 36 | def __init__(self, name, command, arguments=u'0', complete=None, overwrite_exisiting=False): 37 | u""" 38 | :name: The name of command, first character must be uppercase 39 | :command: The actual command that is executed 40 | :arguments: See :h :command-nargs, only the arguments need to be specified 41 | :complete: See :h :command-completion, only the completion arguments need to be specified 42 | """ 43 | object.__init__(self) 44 | 45 | self._name = name 46 | self._command = command 47 | self._arguments = arguments 48 | self._complete = complete 49 | self._overwrite_exisiting = overwrite_exisiting 50 | 51 | def __unicode__(self): 52 | return u':%s' % self.name 53 | 54 | def __str__(self): 55 | return self.__unicode__().encode(u'utf-8') 56 | 57 | @property 58 | def name(self): 59 | return self._name 60 | 61 | @property 62 | def command(self): 63 | return self._command 64 | 65 | @property 66 | def arguments(self): 67 | return self._arguments 68 | 69 | @property 70 | def complete(self): 71 | return self._complete 72 | 73 | @property 74 | def overwrite_exisiting(self): 75 | return self._overwrite_exisiting 76 | 77 | def create(self): 78 | u""" Register/create the command 79 | """ 80 | vim.command((':command%(overwrite)s -nargs=%(arguments)s %(complete)s %(name)s %(command)s' % 81 | {u'overwrite': '!' if self.overwrite_exisiting else '', 82 | u'arguments': self.arguments.encode(u'utf-8'), 83 | u'complete': '-complete=%s' % self.complete.encode(u'utf-8') if self.complete else '', 84 | u'name': self.name, 85 | u'command': self.command} 86 | ).encode(u'utf-8')) 87 | 88 | 89 | class Plug(object): 90 | u""" Represents a to an abitrary command """ 91 | 92 | def __init__(self, name, command, mode=MODE_NORMAL): 93 | u""" 94 | :name: the name of the should be ScriptnameCommandname 95 | :command: the actual command 96 | """ 97 | object.__init__(self) 98 | 99 | if mode not in (MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT, MODE_OPERATOR): 100 | raise ValueError(u'Parameter mode not in MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT, MODE_OPERATOR') 101 | self._mode = mode 102 | 103 | self.name = name 104 | self.command = command 105 | self.created = False 106 | 107 | def __unicode__(self): 108 | return u'%s' % self.name 109 | 110 | def __str__(self): 111 | return self.__unicode__().encode(u'utf-8') 112 | 113 | def create(self): 114 | if not self.created: 115 | self.created = True 116 | cmd = self._mode 117 | if cmd == MODE_ALL: 118 | cmd = u'' 119 | vim.command((u':%snoremap %s %s' % (cmd, str(self), self.command)).encode(u'utf-8')) 120 | 121 | @property 122 | def mode(self): 123 | return self._mode 124 | 125 | 126 | class Keybinding(object): 127 | u""" Representation of a single key binding """ 128 | 129 | def __init__(self, key, action, mode=None, options=None, remap=True, buffer_only=True, silent=True): 130 | u""" 131 | :key: the key(s) action is bound to 132 | :action: the action triggered by key(s) 133 | :mode: definition in which vim modes the key binding is valid. Should be one of MODE_* 134 | :option: list of other options like , ... 135 | :repmap: allow or disallow nested mapping 136 | :buffer_only: define the key binding only for the current buffer 137 | """ 138 | object.__init__(self) 139 | self._key = key 140 | self._action = action 141 | 142 | # grab mode from plug if not set otherwise 143 | if isinstance(self._action, Plug) and not mode: 144 | mode = self._action.mode 145 | 146 | if mode not in (MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT, MODE_OPERATOR): 147 | raise ValueError(u'Parameter mode not in MODE_ALL, MODE_NORMAL, MODE_VISUAL, MODE_INSERT, MODE_OPERATOR') 148 | self._mode = mode 149 | self._options = options 150 | if self._options is None: 151 | self._options = [] 152 | self._remap = remap 153 | self._buffer_only = buffer_only 154 | self._silent = silent 155 | 156 | if self._buffer_only and OPTION_BUFFER_ONLY not in self._options: 157 | self._options.append(OPTION_BUFFER_ONLY) 158 | 159 | if self._silent and OPTION_SLIENT not in self._options: 160 | self._options.append(OPTION_SLIENT) 161 | 162 | @property 163 | def key(self): 164 | return self._key 165 | 166 | @property 167 | def action(self): 168 | return str(self._action) 169 | 170 | @property 171 | def mode(self): 172 | return self._mode 173 | 174 | @property 175 | def options(self): 176 | return self._options[:] 177 | 178 | @property 179 | def remap(self): 180 | return self._remap 181 | 182 | @property 183 | def buffer_only(self): 184 | return self._buffer_only 185 | 186 | @property 187 | def silent(self): 188 | return self._silent 189 | 190 | def create(self): 191 | from orgmode._vim import ORGMODE, echom 192 | 193 | cmd = self._mode 194 | if cmd == MODE_ALL: 195 | cmd = u'' 196 | if not self._remap: 197 | cmd += u'nore' 198 | try: 199 | create_mapping = True 200 | if isinstance(self._action, Plug): 201 | # create plug 202 | self._action.create() 203 | if int(vim.eval((u'hasmapto("%s")' % (self._action, )).encode(u'utf-8'))): 204 | create_mapping = False 205 | if isinstance(self._action, Command): 206 | # create command 207 | self._action.create() 208 | 209 | if create_mapping: 210 | vim.command((u':%smap %s %s %s' % (cmd, u' '.join(self._options), self._key, self._action)).encode(u'utf-8')) 211 | except Exception, e: 212 | if ORGMODE.debug: 213 | echom(u'Failed to register key binding %s %s' % (self._key, self._action)) 214 | 215 | 216 | # vim: set noexpandtab: 217 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/ShowHide.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | from orgmode.liborgmode.headings import Heading 6 | from orgmode._vim import ORGMODE, apply_count 7 | from orgmode import settings 8 | from orgmode.menu import Submenu, ActionEntry 9 | from orgmode.keybinding import Keybinding, Plug, MODE_NORMAL 10 | 11 | 12 | class ShowHide(object): 13 | u""" Show Hide plugin """ 14 | 15 | def __init__(self): 16 | u""" Initialize plugin """ 17 | object.__init__(self) 18 | # menu entries this plugin should create 19 | self.menu = ORGMODE.orgmenu + Submenu(u'&Show Hide') 20 | 21 | # key bindings for this plugin 22 | # key bindings are also registered through the menu so only additional 23 | # bindings should be put in this variable 24 | self.keybindings = [] 25 | 26 | @classmethod 27 | def _fold_depth(cls, h): 28 | """ Find the deepest level of open folds 29 | 30 | :h: Heading 31 | :returns: Tuple (int - level of open folds, boolean - found fold) or None if h is not a Heading 32 | """ 33 | if not isinstance(h, Heading): 34 | return 35 | 36 | if int(vim.eval((u'foldclosed(%d)' % h.start_vim).encode(u'utf-8'))) != -1: 37 | return (h.number_of_parents, True) 38 | 39 | res = [h.number_of_parents + 1] 40 | found = False 41 | for c in h.children: 42 | d, f = cls._fold_depth(c) 43 | res.append(d) 44 | found |= f 45 | 46 | return (max(res), found) 47 | 48 | @classmethod 49 | @apply_count 50 | def toggle_folding(cls, reverse=False): 51 | u""" Toggle folding similar to the way orgmode does 52 | 53 | This is just a convenience function, don't hesitate to use the z* 54 | keybindings vim offers to deal with folding! 55 | 56 | :reverse: If False open folding by one level otherwise close it by one. 57 | """ 58 | d = ORGMODE.get_document() 59 | heading = d.current_heading() 60 | if not heading: 61 | vim.eval(u'feedkeys("", "n")'.encode(u'utf-8')) 62 | return 63 | 64 | cursor = vim.current.window.cursor[:] 65 | 66 | if int(vim.eval((u'foldclosed(%d)' % heading.start_vim).encode(u'utf-8'))) != -1: 67 | if not reverse: 68 | # open closed fold 69 | p = heading.number_of_parents 70 | if not p: 71 | p = heading.level 72 | vim.command((u'normal! %dzo' % p).encode(u'utf-8')) 73 | else: 74 | # reverse folding opens all folds under the cursor 75 | vim.command((u'%d,%dfoldopen!' % (heading.start_vim, heading.end_of_last_child_vim)).encode(u'utf-8')) 76 | vim.current.window.cursor = cursor 77 | return heading 78 | 79 | def open_fold(h): 80 | if h.number_of_parents <= open_depth: 81 | vim.command((u'normal! %dgg%dzo' % (h.start_vim, open_depth)).encode(u'utf-8')) 82 | for c in h.children: 83 | open_fold(c) 84 | 85 | def close_fold(h): 86 | for c in h.children: 87 | close_fold(c) 88 | if h.number_of_parents >= open_depth - 1 and \ 89 | int(vim.eval((u'foldclosed(%d)' % h.start_vim).encode(u'utf-8'))) == -1: 90 | vim.command((u'normal! %dggzc' % (h.start_vim, )).encode(u'utf-8')) 91 | 92 | # find deepest fold 93 | open_depth, found_fold = cls._fold_depth(heading) 94 | 95 | if not reverse: 96 | # recursively open folds 97 | if found_fold: 98 | for child in heading.children: 99 | open_fold(child) 100 | else: 101 | vim.command((u'%d,%dfoldclose!' % (heading.start_vim, heading.end_of_last_child_vim)).encode(u'utf-8')) 102 | 103 | if heading.number_of_parents: 104 | # restore cursor position, it might have been changed by open_fold 105 | vim.current.window.cursor = cursor 106 | 107 | p = heading.number_of_parents 108 | if not p: 109 | p = heading.level 110 | # reopen fold again beacause the former closing of the fold closed all levels, including parents! 111 | vim.command((u'normal! %dzo' % (p, )).encode(u'utf-8')) 112 | else: 113 | # close the last level of folds 114 | close_fold(heading) 115 | 116 | # restore cursor position 117 | vim.current.window.cursor = cursor 118 | return heading 119 | 120 | @classmethod 121 | @apply_count 122 | def global_toggle_folding(cls, reverse=False): 123 | """ Toggle folding globally 124 | 125 | :reverse: If False open folding by one level otherwise close it by one. 126 | """ 127 | d = ORGMODE.get_document() 128 | if reverse: 129 | foldlevel = int(vim.eval(u'&foldlevel'.encode(u'utf-8'))) 130 | if foldlevel == 0: 131 | # open all folds because the user tries to close folds beyound 0 132 | vim.eval(u'feedkeys("zR", "n")'.encode(u'utf-8')) 133 | else: 134 | # vim can reduce the foldlevel on its own 135 | vim.eval(u'feedkeys("zm", "n")'.encode(u'utf-8')) 136 | else: 137 | found = False 138 | for h in d.headings: 139 | res = cls._fold_depth(h) 140 | if res: 141 | found = res[1] 142 | if found: 143 | break 144 | if not found: 145 | # no fold found and the user tries to advance the fold level 146 | # beyond maximum so close everything 147 | vim.eval(u'feedkeys("zM", "n")'.encode(u'utf-8')) 148 | else: 149 | # fold found, vim can increase the foldlevel on its own 150 | vim.eval(u'feedkeys("zr", "n")'.encode(u'utf-8')) 151 | 152 | return d 153 | 154 | def register(self): 155 | u""" 156 | Registration of plugin. Key bindings and other initialization should be done. 157 | """ 158 | # register plug 159 | 160 | self.keybindings.append(Keybinding(u'', Plug(u'OrgToggleFoldingNormal', u':py ORGMODE.plugins[u"ShowHide"].toggle_folding()'))) 161 | self.menu + ActionEntry(u'&Cycle Visibility', self.keybindings[-1]) 162 | 163 | self.keybindings.append(Keybinding(u'', Plug(u'OrgToggleFoldingReverse', u':py ORGMODE.plugins[u"ShowHide"].toggle_folding(reverse=True)'))) 164 | self.menu + ActionEntry(u'Cycle Visibility &Reverse', self.keybindings[-1]) 165 | 166 | self.keybindings.append(Keybinding(u'.', Plug(u'OrgGlobalToggleFoldingNormal', u':py ORGMODE.plugins[u"ShowHide"].global_toggle_folding()'))) 167 | self.menu + ActionEntry(u'Cycle Visibility &Globally', self.keybindings[-1]) 168 | 169 | self.keybindings.append(Keybinding(u',', Plug(u'OrgGlobalToggleFoldingReverse', u':py ORGMODE.plugins[u"ShowHide"].global_toggle_folding(reverse=True)'))) 170 | self.menu + ActionEntry(u'Cycle Visibility Reverse G&lobally', self.keybindings[-1]) 171 | 172 | for i in xrange(0, 10): 173 | self.keybindings.append(Keybinding(u'%d' % (i, ), u'zM:set fdl=%d' % i, mode=MODE_NORMAL)) 174 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/TagsProperties.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | from orgmode._vim import ORGMODE, repeat 6 | from orgmode.menu import Submenu, ActionEntry 7 | from orgmode.keybinding import Keybinding, Plug, Command 8 | from orgmode import settings 9 | 10 | 11 | class TagsProperties(object): 12 | u""" TagsProperties plugin """ 13 | 14 | def __init__(self): 15 | u""" Initialize plugin """ 16 | object.__init__(self) 17 | # menu entries this plugin should create 18 | self.menu = ORGMODE.orgmenu + Submenu(u'&TAGS and Properties') 19 | 20 | # key bindings for this plugin 21 | # key bindings are also registered through the menu so only additional 22 | # bindings should be put in this variable 23 | self.keybindings = [] 24 | 25 | # commands for this plugin 26 | self.commands = [] 27 | 28 | @classmethod 29 | def complete_tags(cls): 30 | u""" build a list of tags and store it in variable b:org_tag_completion 31 | """ 32 | d = ORGMODE.get_document() 33 | heading = d.current_heading() 34 | if not heading: 35 | return 36 | 37 | leading_portion = vim.eval(u'a:ArgLead').decode(u'utf-8') 38 | cursor = int(vim.eval(u'a:CursorPos')) 39 | 40 | # extract currently completed tag 41 | idx_orig = leading_portion.rfind(u':', 0, cursor) 42 | if idx_orig == -1: 43 | idx = 0 44 | else: 45 | idx = idx_orig 46 | 47 | current_tag = leading_portion[idx: cursor].lstrip(u':') 48 | head = leading_portion[:idx + 1] 49 | if idx_orig == -1: 50 | head = u'' 51 | tail = leading_portion[cursor:] 52 | 53 | # extract all tags of the current file 54 | all_tags = set() 55 | for h in d.all_headings(): 56 | for t in h.tags: 57 | all_tags.add(t) 58 | 59 | ignorecase = bool(int(settings.get(u'org_tag_completion_ignorecase', int(vim.eval(u'&ignorecase'))))) 60 | possible_tags = [] 61 | current_tags = heading.tags 62 | for t in all_tags: 63 | if ignorecase: 64 | if t.lower().startswith(current_tag.lower()): 65 | possible_tags.append(t) 66 | elif t.startswith(current_tag): 67 | possible_tags.append(t) 68 | 69 | vim.command((u'let b:org_complete_tags = [%s]' % u', '.join([u'"%s%s:%s"' % (head, i, tail) for i in possible_tags])).encode(u'utf-8')) 70 | 71 | @classmethod 72 | @repeat 73 | def set_tags(cls): 74 | u""" Set tags for current heading 75 | """ 76 | d = ORGMODE.get_document() 77 | heading = d.current_heading() 78 | if not heading: 79 | return 80 | 81 | # retrieve tags 82 | res = None 83 | if heading.tags: 84 | res = vim.eval(u'input("Tags: ", ":%s:", "customlist,Org_complete_tags")' % u':'.join(heading.tags)) 85 | else: 86 | res = vim.eval(u'input("Tags: ", "", "customlist,Org_complete_tags")') 87 | 88 | if res is None: 89 | # user pressed abort any further processing 90 | return 91 | 92 | # remove empty tags 93 | heading.tags = filter(lambda x: x.strip() != u'', res.decode(u'utf-8').strip().strip(u':').split(u':')) 94 | 95 | d.write() 96 | 97 | return u'OrgSetTags' 98 | 99 | @classmethod 100 | def find_tags(cls): 101 | """ Find tags in current file 102 | """ 103 | tags = vim.eval(u'input("Find Tags: ", "", "customlist,Org_complete_tags")') 104 | if tags is None: 105 | # user pressed abort any further processing 106 | return 107 | 108 | tags = filter(lambda x: x.strip() != u'', tags.decode(u'utf-8').strip().strip(u':').split(u':')) 109 | if tags: 110 | searchstring = u'\\(' 111 | first = True 112 | for t1 in tags: 113 | if first: 114 | first = False 115 | searchstring += u'%s' % t1 116 | else: 117 | searchstring += u'\\|%s' % t1 118 | 119 | for t2 in tags: 120 | if t1 == t2: 121 | continue 122 | searchstring += u'\\(:[a-zA-Z:]*\\)\?:%s' % t2 123 | searchstring += u'\\)' 124 | 125 | vim.command(u'/\\zs:%s:\\ze' % searchstring) 126 | return u'OrgFindTags' 127 | 128 | @classmethod 129 | def realign_tags(cls): 130 | u""" 131 | Updates tags when user finished editing a heading 132 | """ 133 | d = ORGMODE.get_document(allow_dirty=True) 134 | heading = d.find_current_heading() 135 | if not heading: 136 | return 137 | 138 | if vim.current.window.cursor[0] == heading.start_vim: 139 | heading.set_dirty_heading() 140 | d.write_heading(heading, including_children=False) 141 | 142 | @classmethod 143 | def realign_all_tags(cls): 144 | u""" 145 | Updates tags when user finishes editing a heading 146 | """ 147 | d = ORGMODE.get_document() 148 | for heading in d.all_headings(): 149 | heading.set_dirty_heading() 150 | 151 | d.write() 152 | 153 | def register(self): 154 | u""" 155 | Registration of plugin. Key bindings and other initialization should be done. 156 | """ 157 | # an Action menu entry which binds "keybinding" to action ":action" 158 | settings.set(u'org_tag_column', vim.eval(u'&textwidth')) 159 | settings.set(u'org_tag_completion_ignorecase', int(vim.eval(u'&ignorecase'))) 160 | 161 | cmd = Command( 162 | u'OrgSetTags', 163 | u':py ORGMODE.plugins[u"TagsProperties"].set_tags()') 164 | self.commands.append(cmd) 165 | keybinding = Keybinding( 166 | u'st', 167 | Plug(u'OrgSetTags', cmd)) 168 | self.keybindings.append(keybinding) 169 | self.menu + ActionEntry(u'Set &Tags', keybinding) 170 | 171 | cmd = Command( 172 | u'OrgFindTags', 173 | u':py ORGMODE.plugins[u"TagsProperties"].find_tags()') 174 | self.commands.append(cmd) 175 | keybinding = Keybinding( 176 | u'ft', 177 | Plug(u'OrgFindTags', cmd)) 178 | self.keybindings.append(keybinding) 179 | self.menu + ActionEntry(u'&Find Tags', keybinding) 180 | 181 | cmd = Command( 182 | u'OrgTagsRealign', 183 | u":py ORGMODE.plugins[u'TagsProperties'].realign_all_tags()") 184 | self.commands.append(cmd) 185 | 186 | # workaround to align tags when user is leaving insert mode 187 | vim.command(u"""function Org_complete_tags(ArgLead, CmdLine, CursorPos) 188 | python << EOF 189 | ORGMODE.plugins[u'TagsProperties'].complete_tags() 190 | EOF 191 | if exists('b:org_complete_tags') 192 | let tmp = b:org_complete_tags 193 | unlet b:org_complete_tags 194 | return tmp 195 | else 196 | return [] 197 | endif 198 | endfunction""".encode(u'utf-8')) 199 | 200 | # this is for all org files opened after this file 201 | vim.command(u"au orgmode FileType org :au orgmode InsertLeave :py ORGMODE.plugins[u'TagsProperties'].realign_tags()".encode(u'utf-8')) 202 | 203 | # this is for the current file 204 | vim.command(u"au orgmode InsertLeave :py ORGMODE.plugins[u'TagsProperties'].realign_tags()".encode(u'utf-8')) 205 | 206 | # vim: set noexpandtab: 207 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/Hyperlinks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | 5 | import vim 6 | 7 | from orgmode._vim import echom, ORGMODE, realign_tags 8 | from orgmode.menu import Submenu, Separator, ActionEntry 9 | from orgmode.keybinding import Keybinding, Plug, Command 10 | 11 | 12 | class Hyperlinks(object): 13 | u""" Hyperlinks plugin """ 14 | 15 | def __init__(self): 16 | u""" Initialize plugin """ 17 | object.__init__(self) 18 | # menu entries this plugin should create 19 | self.menu = ORGMODE.orgmenu + Submenu(u'Hyperlinks') 20 | 21 | # key bindings for this plugin 22 | # key bindings are also registered through the menu so only additional 23 | # bindings should be put in this variable 24 | self.keybindings = [] 25 | 26 | # commands for this plugin 27 | self.commands = [] 28 | 29 | uri_match = re.compile( 30 | r'^\[{2}(?P[^][]*)(\]\[(?P[^][]*))?\]{2}') 31 | 32 | @classmethod 33 | def _get_link(cls, cursor=None): 34 | u""" 35 | Get the link the cursor is on and return it's URI and description 36 | 37 | :cursor: None or (Line, Column) 38 | :returns: None if no link was found, otherwise {uri:URI, 39 | description:DESCRIPTION, line:LINE, start:START, end:END} 40 | or uri and description could be None if not set 41 | """ 42 | cursor = cursor if cursor else vim.current.window.cursor 43 | line = vim.current.buffer[cursor[0] - 1].decode(u'utf-8') 44 | 45 | # if the cursor is on the last bracket, it's not recognized as a hyperlink 46 | start = line.rfind(u'[[', 0, cursor[1]) 47 | if start == -1: 48 | start = line.rfind(u'[[', 0, cursor[1] + 2) 49 | end = line.find(u']]', cursor[1]) 50 | if end == -1: 51 | end = line.find(u']]', cursor[1] - 1) 52 | 53 | # extract link 54 | if start != -1 and end != -1: 55 | end += 2 56 | match = Hyperlinks.uri_match.match(line[start:end]) 57 | 58 | res = { 59 | u'line': line, 60 | u'start': start, 61 | u'end': end, 62 | u'uri': None, 63 | u'description': None} 64 | if match: 65 | res.update(match.groupdict()) 66 | return res 67 | 68 | @classmethod 69 | def follow(cls, action=u'openLink', visual=u''): 70 | u""" Follow hyperlink. If called on a regular string UTL determines the 71 | outcome. Normally a file with that name will be opened. 72 | 73 | :action: "copy" if the link should be copied to clipboard, otherwise 74 | the link will be opened 75 | :visual: "visual" if Universal Text Linking should be triggered in 76 | visual mode 77 | 78 | :returns: URI or None 79 | """ 80 | if not int(vim.eval(u'exists(":Utl")')): 81 | echom(u'Universal Text Linking plugin not installed, unable to proceed.') 82 | return 83 | 84 | action = u'copyLink' \ 85 | if (action and action.startswith(u'copy')) \ 86 | else u'openLink' 87 | visual = u'visual' if visual and visual.startswith(u'visual') else u'' 88 | 89 | link = Hyperlinks._get_link() 90 | 91 | if link and link[u'uri'] is not None: 92 | # call UTL with the URI 93 | vim.command(( 94 | u'Utl %s %s %s' % (action, visual, link[u'uri'])).encode(u'utf-8')) 95 | return link[u'uri'] 96 | else: 97 | # call UTL and let it decide what to do 98 | vim.command((u'Utl %s %s' % (action, visual)).encode(u'utf-8')) 99 | 100 | @classmethod 101 | @realign_tags 102 | def insert(cls, uri=None, description=None): 103 | u""" Inserts a hyperlink. If no arguments are provided, an interactive 104 | query will be started. 105 | 106 | :uri: The URI that will be opened 107 | :description: An optional description that will be displayed instead of 108 | the URI 109 | 110 | :returns: (URI, description) 111 | """ 112 | link = Hyperlinks._get_link() 113 | if link: 114 | if uri is None and link[u'uri'] is not None: 115 | uri = link[u'uri'] 116 | if description is None and link[u'description'] is not None: 117 | description = link[u'description'] 118 | 119 | if uri is None: 120 | uri = vim.eval(u'input("Link: ", "", "file")') 121 | elif link: 122 | uri = vim.eval(u'input("Link: ", "%s", "file")' % link[u'uri']) 123 | if uri is None: 124 | return 125 | else: 126 | uri = uri.decode(u'utf-8') 127 | 128 | if description is None: 129 | description = vim.eval(u'input("Description: ")').decode(u'utf-8') 130 | elif link: 131 | description = vim.eval( 132 | u'input("Description: ", "%s")' % 133 | link[u'description']).decode(u'utf-8') 134 | if description is None: 135 | return 136 | 137 | cursor = vim.current.window.cursor 138 | cl = vim.current.buffer[cursor[0] - 1].decode(u'utf-8') 139 | head = cl[:cursor[1] + 1] if not link else cl[:link[u'start']] 140 | tail = cl[cursor[1] + 1:] if not link else cl[link[u'end']:] 141 | 142 | separator = u'' 143 | if description: 144 | separator = u'][' 145 | 146 | if uri or description: 147 | vim.current.buffer[cursor[0] - 1] = \ 148 | (u''.join((head, u'[[%s%s%s]]' % 149 | (uri, separator, description), tail))).encode(u'utf-8') 150 | elif link: 151 | vim.current.buffer[cursor[0] - 1] = \ 152 | (u''.join((head, tail))).encode(u'utf-8') 153 | 154 | def register(self): 155 | u""" 156 | Registration of plugin. Key bindings and other initialization should be done. 157 | """ 158 | cmd = Command( 159 | u'OrgHyperlinkFollow', 160 | u':py ORGMODE.plugins[u"Hyperlinks"].follow()') 161 | self.commands.append(cmd) 162 | self.keybindings.append( 163 | Keybinding(u'gl', Plug(u'OrgHyperlinkFollow', self.commands[-1]))) 164 | self.menu + ActionEntry(u'&Follow Link', self.keybindings[-1]) 165 | 166 | cmd = Command( 167 | u'OrgHyperlinkCopy', 168 | u':py ORGMODE.plugins[u"Hyperlinks"].follow(action=u"copy")') 169 | self.commands.append(cmd) 170 | self.keybindings.append( 171 | Keybinding(u'gyl', Plug(u'OrgHyperlinkCopy', self.commands[-1]))) 172 | self.menu + ActionEntry(u'&Copy Link', self.keybindings[-1]) 173 | 174 | cmd = Command( 175 | u'OrgHyperlinkInsert', 176 | u':py ORGMODE.plugins[u"Hyperlinks"].insert()', 177 | arguments=u'*') 178 | self.commands.append(cmd) 179 | self.keybindings.append( 180 | Keybinding(u'gil', Plug(u'OrgHyperlinkInsert', self.commands[-1]))) 181 | self.menu + ActionEntry(u'&Insert Link', self.keybindings[-1]) 182 | 183 | self.menu + Separator() 184 | 185 | # find next link 186 | cmd = Command( 187 | u'OrgHyperlinkNextLink', 188 | u":if search('\[\{2}\zs[^][]*\(\]\[[^][]*\)\?\ze\]\{2}', 's') == 0 | echo 'No further link found.' | endif") 189 | self.commands.append(cmd) 190 | self.keybindings.append( 191 | Keybinding(u'gn', Plug(u'OrgHyperlinkNextLink', self.commands[-1]))) 192 | self.menu + ActionEntry(u'&Next Link', self.keybindings[-1]) 193 | 194 | # find previous link 195 | cmd = Command( 196 | u'OrgHyperlinkPreviousLink', 197 | u":if search('\[\{2}\zs[^][]*\(\]\[[^][]*\)\?\ze\]\{2}', 'bs') == 0 | echo 'No further link found.' | endif") 198 | self.commands.append(cmd) 199 | self.keybindings.append( 200 | Keybinding(u'go', Plug(u'OrgHyperlinkPreviousLink', self.commands[-1]))) 201 | self.menu + ActionEntry(u'&Previous Link', self.keybindings[-1]) 202 | 203 | self.menu + Separator() 204 | 205 | # Descriptive Links 206 | cmd = Command(u'OrgHyperlinkDescriptiveLinks', u':setlocal cole=2') 207 | self.commands.append(cmd) 208 | self.menu + ActionEntry(u'&Descriptive Links', self.commands[-1]) 209 | 210 | # Literal Links 211 | cmd = Command(u'OrgHyperlinkLiteralLinks', u':setlocal cole=0') 212 | self.commands.append(cmd) 213 | self.menu + ActionEntry(u'&Literal Links', self.commands[-1]) 214 | 215 | # vim: set noexpandtab: 216 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/Misc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | from orgmode._vim import ORGMODE, apply_count 6 | from orgmode.menu import Submenu 7 | from orgmode.keybinding import Keybinding, Plug, MODE_VISUAL, MODE_OPERATOR 8 | 9 | 10 | class Misc(object): 11 | u""" Miscellaneous functionality """ 12 | 13 | def __init__(self): 14 | u""" Initialize plugin """ 15 | object.__init__(self) 16 | # menu entries this plugin should create 17 | self.menu = ORGMODE.orgmenu + Submenu(u'Misc') 18 | 19 | # key bindings for this plugin 20 | # key bindings are also registered through the menu so only additional 21 | # bindings should be put in this variable 22 | self.keybindings = [] 23 | 24 | @classmethod 25 | def jump_to_first_character(cls): 26 | heading = ORGMODE.get_document().current_heading() 27 | if not heading or heading.start_vim != vim.current.window.cursor[0]: 28 | vim.eval(u'feedkeys("^", "n")'.encode(u'utf-8')) 29 | return 30 | 31 | vim.current.window.cursor = (vim.current.window.cursor[0], heading.level + 1) 32 | 33 | @classmethod 34 | def edit_at_first_character(cls): 35 | heading = ORGMODE.get_document().current_heading() 36 | if not heading or heading.start_vim != vim.current.window.cursor[0]: 37 | vim.eval(u'feedkeys("I", "n")'.encode(u'utf-8')) 38 | return 39 | 40 | vim.current.window.cursor = (vim.current.window.cursor[0], heading.level + 1) 41 | vim.command(u'startinsert'.encode(u'utf-8')) 42 | 43 | # @repeat 44 | @classmethod 45 | @apply_count 46 | def i_heading(cls, mode=u'visual', selection=u'inner', skip_children=False): 47 | u""" 48 | inner heading text object 49 | """ 50 | heading = ORGMODE.get_document().current_heading() 51 | if heading: 52 | if selection != u'inner': 53 | heading = heading if not heading.parent else heading.parent 54 | 55 | line_start, col_start = [int(i) for i in vim.eval(u'getpos("\'<")'.encode(u'utf-8'))[1:3]] 56 | line_end, col_end = [int(i) for i in vim.eval(u'getpos("\'>")'.encode(u'utf-8'))[1:3]] 57 | 58 | if mode != u'visual': 59 | line_start = vim.current.window.cursor[0] 60 | line_end = line_start 61 | 62 | start = line_start 63 | end = line_end 64 | move_one_character_back = u'' if mode == u'visual' else u'h' 65 | 66 | if heading.start_vim < line_start: 67 | start = heading.start_vim 68 | if heading.end_vim > line_end and not skip_children: 69 | end = heading.end_vim 70 | elif heading.end_of_last_child_vim > line_end and skip_children: 71 | end = heading.end_of_last_child_vim 72 | 73 | if mode != u'visual' and not vim.current.buffer[end - 1]: 74 | end -= 1 75 | move_one_character_back = u'' 76 | 77 | swap_cursor = u'o' if vim.current.window.cursor[0] == line_start else u'' 78 | 79 | if selection == u'inner' and vim.current.window.cursor[0] != line_start: 80 | h = ORGMODE.get_document().current_heading() 81 | if h: 82 | heading = h 83 | 84 | visualmode = vim.eval(u'visualmode()').decode(u'utf-8') if mode == u'visual' else u'v' 85 | 86 | if line_start == start and line_start != heading.start_vim: 87 | if col_start in (0, 1): 88 | vim.command( 89 | (u'normal! %dgg0%s%dgg$%s%s' % 90 | (start, visualmode, end, move_one_character_back, swap_cursor)).encode(u'utf-8')) 91 | else: 92 | vim.command( 93 | (u'normal! %dgg0%dl%s%dgg$%s%s' % 94 | (start, col_start - 1, visualmode, end, move_one_character_back, swap_cursor)).encode(u'utf-8')) 95 | else: 96 | vim.command( 97 | (u'normal! %dgg0%dl%s%dgg$%s%s' % 98 | (start, heading.level + 1, visualmode, end, move_one_character_back, swap_cursor)).encode(u'utf-8')) 99 | 100 | if selection == u'inner': 101 | if mode == u'visual': 102 | return u'OrgInnerHeadingVisual' if not skip_children else u'OrgInnerTreeVisual' 103 | else: 104 | return u'OrgInnerHeadingOperator' if not skip_children else u'OrgInnerTreeOperator' 105 | else: 106 | if mode == u'visual': 107 | return u'OrgOuterHeadingVisual' if not skip_children else u'OrgOuterTreeVisual' 108 | else: 109 | return u'OrgOuterHeadingOperator' if not skip_children else u'OrgOuterTreeOperator' 110 | elif mode == u'visual': 111 | vim.command(u'normal! gv'.encode(u'utf-8')) 112 | 113 | # @repeat 114 | @classmethod 115 | @apply_count 116 | def a_heading(cls, selection=u'inner', skip_children=False): 117 | u""" 118 | a heading text object 119 | """ 120 | heading = ORGMODE.get_document().current_heading() 121 | if heading: 122 | if selection != u'inner': 123 | heading = heading if not heading.parent else heading.parent 124 | 125 | line_start, col_start = [int(i) for i in vim.eval(u'getpos("\'<")'.encode(u'utf-8'))[1:3]] 126 | line_end, col_end = [int(i) for i in vim.eval(u'getpos("\'>")'.encode(u'utf-8'))[1:3]] 127 | 128 | start = line_start 129 | end = line_end 130 | 131 | if heading.start_vim < line_start: 132 | start = heading.start_vim 133 | if heading.end_vim > line_end and not skip_children: 134 | end = heading.end_vim 135 | elif heading.end_of_last_child_vim > line_end and skip_children: 136 | end = heading.end_of_last_child_vim 137 | 138 | swap_cursor = u'o' if vim.current.window.cursor[0] == line_start else u'' 139 | 140 | vim.command( 141 | (u'normal! %dgg%s%dgg$%s' % 142 | (start, vim.eval(u'visualmode()'.encode(u'utf-8')), end, swap_cursor)).encode(u'utf-8')) 143 | if selection == u'inner': 144 | return u'OrgAInnerHeadingVisual' if not skip_children else u'OrgAInnerTreeVisual' 145 | else: 146 | return u'OrgAOuterHeadingVisual' if not skip_children else u'OrgAOuterTreeVisual' 147 | else: 148 | vim.command(u'normal! gv'.encode(u'utf-8')) 149 | 150 | def register(self): 151 | u""" 152 | Registration of plugin. Key bindings and other initialization should be done. 153 | """ 154 | self.keybindings.append(Keybinding(u'^', Plug(u'OrgJumpToFirstCharacter', u':py ORGMODE.plugins[u"Misc"].jump_to_first_character()'))) 155 | self.keybindings.append(Keybinding(u'I', Plug(u'OrgEditAtFirstCharacter', u':py ORGMODE.plugins[u"Misc"].edit_at_first_character()'))) 156 | 157 | self.keybindings.append(Keybinding(u'ih', Plug(u'OrgInnerHeadingVisual', u':py ORGMODE.plugins[u"Misc"].i_heading()', mode=MODE_VISUAL))) 158 | self.keybindings.append(Keybinding(u'ah', Plug(u'OrgAInnerHeadingVisual', u':py ORGMODE.plugins[u"Misc"].a_heading()', mode=MODE_VISUAL))) 159 | self.keybindings.append(Keybinding(u'Oh', Plug(u'OrgOuterHeadingVisual', u':py ORGMODE.plugins[u"Misc"].i_heading(selection=u"outer")', mode=MODE_VISUAL))) 160 | self.keybindings.append(Keybinding(u'OH', Plug(u'OrgAOuterHeadingVisual', u':py ORGMODE.plugins[u"Misc"].a_heading(selection=u"outer")', mode=MODE_VISUAL))) 161 | 162 | self.keybindings.append(Keybinding(u'ih', Plug(u'OrgInnerHeadingOperator', u':py ORGMODE.plugins[u"Misc"].i_heading(mode=u"operator")', mode=MODE_OPERATOR))) 163 | self.keybindings.append(Keybinding(u'ah', u':normal Vah', mode=MODE_OPERATOR)) 164 | self.keybindings.append(Keybinding(u'Oh', Plug(u'OrgOuterHeadingOperator', ':py ORGMODE.plugins[u"Misc"].i_heading(mode=u"operator", selection=u"outer")', mode=MODE_OPERATOR))) 165 | self.keybindings.append(Keybinding(u'OH', u':normal VOH', mode=MODE_OPERATOR)) 166 | 167 | self.keybindings.append(Keybinding(u'ir', Plug(u'OrgInnerTreeVisual', u':py ORGMODE.plugins[u"Misc"].i_heading(skip_children=True)', mode=MODE_VISUAL))) 168 | self.keybindings.append(Keybinding(u'ar', Plug(u'OrgAInnerTreeVisual', u':py ORGMODE.plugins[u"Misc"].a_heading(skip_children=True)', mode=MODE_VISUAL))) 169 | self.keybindings.append(Keybinding(u'Or', Plug(u'OrgOuterTreeVisual', u'<:py ORGMODE.plugins[u"Misc"].i_heading(selection=u"outer", skip_children=True)', mode=MODE_VISUAL))) 170 | self.keybindings.append(Keybinding(u'OR', Plug(u'OrgAOuterTreeVisual', u':py ORGMODE.plugins[u"Misc"].a_heading(selection=u"outer", skip_children=True)', mode=MODE_VISUAL))) 171 | 172 | self.keybindings.append(Keybinding(u'ir', Plug(u'OrgInnerTreeOperator', u':py ORGMODE.plugins[u"Misc"].i_heading(mode=u"operator")', mode=MODE_OPERATOR))) 173 | self.keybindings.append(Keybinding(u'ar', u':normal Var', mode=MODE_OPERATOR)) 174 | self.keybindings.append(Keybinding(u'Or', Plug(u'OrgOuterTreeOperator', u':py ORGMODE.plugins[u"Misc"].i_heading(mode=u"operator", selection=u"outer", skip_children=True)', mode=MODE_OPERATOR))) 175 | self.keybindings.append(Keybinding(u'OR', u':normal VOR', mode=MODE_OPERATOR)) 176 | 177 | # vim: set noexpandtab: 178 | -------------------------------------------------------------------------------- /ftplugin/orgmode/liborgmode/documents.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | documents 5 | ~~~~~~~~~ 6 | 7 | TODO: explain this :) 8 | """ 9 | 10 | from UserList import UserList 11 | 12 | from orgmode.liborgmode.base import MultiPurposeList, flatten_list, Direction, get_domobj_range 13 | from orgmode.liborgmode.headings import Heading, HeadingList 14 | 15 | 16 | class Document(object): 17 | u""" 18 | Representation of a whole org-mode document. 19 | 20 | A Document consists basically of headings (see Headings) and some metadata. 21 | 22 | TODO: explain the 'dirty' mechanism 23 | """ 24 | 25 | def __init__(self): 26 | u""" 27 | Don't call this constructor directly but use one of the concrete 28 | implementations. 29 | 30 | TODO: what are the concrete implementatiions? 31 | """ 32 | object.__init__(self) 33 | 34 | # is a list - only the Document methods should work on this list! 35 | self._content = None 36 | self._dirty_meta_information = False 37 | self._dirty_document = False 38 | self._meta_information = MultiPurposeList(on_change=self.set_dirty_meta_information) 39 | self._orig_meta_information_len = None 40 | self._headings = HeadingList(obj=self) 41 | self._deleted_headings = [] 42 | 43 | # settings needed to align tags properly 44 | self._tabstop = 8 45 | self._tag_column = 77 46 | 47 | self.todo_states = [u'TODO', u'DONE'] 48 | 49 | def __unicode__(self): 50 | if self.meta_information is None: 51 | return u'\n'.join(self.all_headings()) 52 | return u'\n'.join(self.meta_information) + u'\n' + u'\n'.join([u'\n'.join([unicode(i)] + i.body) for i in self.all_headings()]) 53 | 54 | def __str__(self): 55 | return self.__unicode__().encode(u'utf-8') 56 | 57 | def get_all_todo_states(self): 58 | u""" Convenience function that returns all todo and done states and 59 | sequences in one big list. 60 | 61 | :returns: [all todo/done states] 62 | """ 63 | return flatten_list(self.get_todo_states()) 64 | 65 | def get_todo_states(self): 66 | u""" Returns a list containing a tuple of two lists of allowed todo 67 | states split by todo and done states. Multiple todo-done state 68 | sequences can be defined. 69 | 70 | :returns: [([todo states], [done states]), ..] 71 | """ 72 | return self.todo_states 73 | 74 | def tabstop(): 75 | u""" Tabstop for this document """ 76 | def fget(self): 77 | return self._tabstop 78 | 79 | def fset(self, value): 80 | self._tabstop = value 81 | 82 | return locals() 83 | tabstop = property(**tabstop()) 84 | 85 | def tag_column(): 86 | u""" The column all tags are right-aligned to """ 87 | def fget(self): 88 | return self._tag_column 89 | 90 | def fset(self, value): 91 | self._tag_column = value 92 | 93 | return locals() 94 | tag_column = property(**tag_column()) 95 | 96 | def init_dom(self, heading=Heading): 97 | u""" Initialize all headings in document - build DOM. This method 98 | should be call prior to accessing the document. 99 | 100 | :returns: self 101 | """ 102 | def init_heading(_h): 103 | u""" 104 | :returns the initialized heading 105 | """ 106 | start = _h.end + 1 107 | prev_heading = None 108 | while True: 109 | new_heading = self.find_heading(start, heading=heading) 110 | 111 | # * Heading 1 <- heading 112 | # * Heading 1 <- sibling 113 | # or 114 | # * Heading 2 <- heading 115 | # * Heading 1 <- parent's sibling 116 | if not new_heading or \ 117 | new_heading.level <= _h.level: 118 | break 119 | 120 | # * Heading 1 <- heading 121 | # * Heading 2 <- first child 122 | # * Heading 2 <- another child 123 | new_heading._parent = _h 124 | if prev_heading: 125 | prev_heading._next_sibling = new_heading 126 | new_heading._previous_sibling = prev_heading 127 | _h.children.data.append(new_heading) 128 | # the start and end computation is only 129 | # possible when the new heading was properly 130 | # added to the document structure 131 | init_heading(new_heading) 132 | if new_heading.children: 133 | # skip children 134 | start = new_heading.end_of_last_child + 1 135 | else: 136 | start = new_heading.end + 1 137 | prev_heading = new_heading 138 | 139 | return _h 140 | 141 | h = self.find_heading(heading=heading) 142 | # initialize meta information 143 | if h: 144 | self._meta_information.data.extend(self._content[:h._orig_start]) 145 | else: 146 | self._meta_information.data.extend(self._content[:]) 147 | self._orig_meta_information_len = len(self.meta_information) 148 | 149 | # initialize dom tree 150 | prev_h = None 151 | while h: 152 | if prev_h: 153 | prev_h._next_sibling = h 154 | h._previous_sibling = prev_h 155 | self.headings.data.append(h) 156 | init_heading(h) 157 | prev_h = h 158 | h = self.find_heading(h.end_of_last_child + 1, heading=heading) 159 | 160 | return self 161 | 162 | def meta_information(): 163 | u""" 164 | Meta information is text that precedes all headings in an org-mode 165 | document. It might contain additional information about the document, 166 | e.g. author 167 | """ 168 | def fget(self): 169 | return self._meta_information 170 | 171 | def fset(self, value): 172 | if self._orig_meta_information_len is None: 173 | self._orig_meta_information_len = len(self.meta_information) 174 | if type(value) in (list, tuple) or isinstance(value, UserList): 175 | self._meta_information[:] = flatten_list(value) 176 | elif type(value) in (str, ): 177 | self._meta_information[:] = value.decode(u'utf-8').split(u'\n') 178 | elif type(value) in (unicode, ): 179 | self._meta_information[:] = value.split(u'\n') 180 | self.set_dirty_meta_information() 181 | 182 | def fdel(self): 183 | self.meta_information = u'' 184 | 185 | return locals() 186 | meta_information = property(**meta_information()) 187 | 188 | def headings(): 189 | u""" List of top level headings """ 190 | def fget(self): 191 | return self._headings 192 | 193 | def fset(self, value): 194 | self._headings[:] = value 195 | 196 | def fdel(self): 197 | del self.headings[:] 198 | 199 | return locals() 200 | headings = property(**headings()) 201 | 202 | def write(self): 203 | u""" write the document 204 | 205 | :returns: True if something was written, otherwise False 206 | """ 207 | raise NotImplementedError(u'Abstract method, please use concrete impelementation!') 208 | 209 | def set_dirty_meta_information(self): 210 | u""" Mark the meta information dirty so that it will be rewritten when 211 | saving the document """ 212 | self._dirty_meta_information = True 213 | 214 | def set_dirty_document(self): 215 | u""" Mark the whole document dirty. When changing a heading this 216 | method must be executed in order to changed computation of start and 217 | end positions from a static to a dynamic computation """ 218 | self._dirty_document = True 219 | 220 | @property 221 | def is_dirty(self): 222 | u""" 223 | Return information about unsaved changes for the document and all 224 | related headings. 225 | 226 | :returns: Return True if document contains unsaved changes. 227 | """ 228 | if self.is_dirty_meta_information: 229 | return True 230 | 231 | if self.is_dirty_document: 232 | return True 233 | 234 | if self._deleted_headings: 235 | return True 236 | 237 | return False 238 | 239 | @property 240 | def is_dirty_meta_information(self): 241 | u""" Return True if the meta information is marked dirty """ 242 | return self._dirty_meta_information 243 | 244 | @property 245 | def is_dirty_document(self): 246 | u""" Return True if the document is marked dirty """ 247 | return self._dirty_document 248 | 249 | def all_headings(self): 250 | u""" Iterate over all headings of the current document in serialized 251 | order 252 | 253 | :returns: Returns an iterator object which returns all headings of 254 | the current file in serialized order 255 | """ 256 | if not self.headings: 257 | raise StopIteration() 258 | 259 | h = self.headings[0] 260 | while h: 261 | yield h 262 | h = h.next_heading 263 | raise StopIteration() 264 | 265 | def find_heading( 266 | self, position=0, direction=Direction.FORWARD, 267 | heading=Heading, connect_with_document=True): 268 | u""" Find heading in the given direction 269 | 270 | :postition: starting line, counting from 0 (in vim you start 271 | counting from 1, don't forget) 272 | :direction: downwards == Direction.FORWARD, 273 | upwards == Direction.BACKWARD 274 | :heading: Heading class from which new heading objects will be 275 | instanciated 276 | :connect_with_document: if True, the newly created heading will be 277 | connected with the document, otherwise not 278 | 279 | :returns: New heading object or None 280 | """ 281 | (start, end) = get_domobj_range(content=self._content, position=position, direction=direction, identify_fun=heading.identify_heading) 282 | 283 | if start is not None and end is None: 284 | end = len(self._content) - 1 285 | if start is not None and end is not None: 286 | return heading.parse_heading_from_data( 287 | self._content[start:end + 1], self.get_all_todo_states(), 288 | document=self if connect_with_document else None, orig_start=start) 289 | 290 | 291 | # vim: set noexpandtab: 292 | -------------------------------------------------------------------------------- /ftplugin/orgmode/liborgmode/orgdate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | u""" 3 | OrgDate 4 | ~~~~~~~~~~~~~~~~~~ 5 | 6 | This module contains all date/time/timerange representations that exist in 7 | orgmode. 8 | 9 | There exist three different kinds: 10 | 11 | * OrgDate: is similar to a date object in python and it looks like 12 | '2011-09-07 Wed'. 13 | 14 | * OrgDateTime: is similar to a datetime object in python and looks like 15 | '2011-09-07 Wed 10:30' 16 | 17 | * OrgTimeRange: indicates a range of time. It has a start and and end date: 18 | * <2011-09-07 Wed>--<2011-09-08 Fri> 19 | * <2011-09-07 Wed 10:00-13:00> 20 | 21 | All OrgTime oblects can be active or inactive. 22 | """ 23 | 24 | import datetime 25 | import re 26 | 27 | # <2011-09-12 Mon> 28 | _DATE_REGEX = re.compile(r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w>") 29 | # [2011-09-12 Mon] 30 | _DATE_PASSIVE_REGEX = re.compile(r"\[(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w\]") 31 | 32 | # <2011-09-12 Mon 10:20> 33 | _DATETIME_REGEX = re.compile( 34 | r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d{1,2}):(\d\d)>") 35 | # [2011-09-12 Mon 10:20] 36 | _DATETIME_PASSIVE_REGEX = re.compile( 37 | r"\[(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d{1,2}):(\d\d)\]") 38 | 39 | # <2011-09-12 Mon>--<2011-09-13 Tue> 40 | _DATERANGE_REGEX = re.compile( 41 | # <2011-09-12 Mon>-- 42 | r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w>--" 43 | # <2011-09-13 Tue> 44 | "<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w>") 45 | # <2011-09-12 Mon 10:00>--<2011-09-12 Mon 11:00> 46 | _DATETIMERANGE_REGEX = re.compile( 47 | # <2011-09-12 Mon 10:00>-- 48 | r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d\d):(\d\d)>--" 49 | # <2011-09-12 Mon 11:00> 50 | "<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d\d):(\d\d)>") 51 | # <2011-09-12 Mon 10:00--12:00> 52 | _DATETIMERANGE_SAME_DAY_REGEX = re.compile( 53 | r"<(\d\d\d\d)-(\d\d)-(\d\d) [A-Z]\w\w (\d\d):(\d\d)-(\d\d):(\d\d)>") 54 | 55 | 56 | def get_orgdate(data): 57 | u""" 58 | Parse the given data (can be a string or list). Return an OrgDate if data 59 | contains a string representation of an OrgDate; otherwise return None. 60 | 61 | data can be a string or a list containing strings. 62 | """ 63 | if isinstance(data, list): 64 | return _findfirst(_text2orgdate, data) 65 | else: 66 | return _text2orgdate(data) 67 | # if no dates found 68 | return None 69 | 70 | 71 | def _findfirst(f, seq): 72 | u""" 73 | Return first item in sequence seq where f(item) == True. 74 | 75 | TODO: this is a general help function and it should be moved somewhere 76 | else; preferably into the standard lib :) 77 | """ 78 | for found in (f(item) for item in seq if f(item)): 79 | return found 80 | 81 | 82 | def _text2orgdate(string): 83 | u""" 84 | Transform the given string into an OrgDate. 85 | Return an OrgDate if data contains a string representation of an OrgDate; 86 | otherwise return None. 87 | """ 88 | # handle active datetime with same day 89 | result = _DATETIMERANGE_SAME_DAY_REGEX.search(string) 90 | if result: 91 | try: 92 | (syear, smonth, sday, shour, smin, ehour, emin) = \ 93 | [int(m) for m in result.groups()] 94 | start = datetime.datetime(syear, smonth, sday, shour, smin) 95 | end = datetime.datetime(syear, smonth, sday, ehour, emin) 96 | return OrgTimeRange(True, start, end) 97 | except Exception: 98 | return None 99 | 100 | # handle active datetime 101 | result = _DATETIMERANGE_REGEX.search(string) 102 | if result: 103 | try: 104 | tmp = [int(m) for m in result.groups()] 105 | (syear, smonth, sday, shour, smin, eyear, emonth, eday, ehour, emin) = tmp 106 | start = datetime.datetime(syear, smonth, sday, shour, smin) 107 | end = datetime.datetime(eyear, emonth, eday, ehour, emin) 108 | return OrgTimeRange(True, start, end) 109 | except Exception: 110 | return None 111 | 112 | # handle active datetime 113 | result = _DATERANGE_REGEX.search(string) 114 | if result: 115 | try: 116 | tmp = [int(m) for m in result.groups()] 117 | syear, smonth, sday, eyear, emonth, ehour = tmp 118 | start = datetime.date(syear, smonth, sday) 119 | end = datetime.date(eyear, emonth, ehour) 120 | return OrgTimeRange(True, start, end) 121 | except Exception: 122 | return None 123 | 124 | # handle active datetime 125 | result = _DATETIME_REGEX.search(string) 126 | if result: 127 | try: 128 | year, month, day, hour, minutes = [int(m) for m in result.groups()] 129 | return OrgDateTime(True, year, month, day, hour, minutes) 130 | except Exception: 131 | return None 132 | 133 | # handle passive datetime 134 | result = _DATETIME_PASSIVE_REGEX.search(string) 135 | if result: 136 | try: 137 | year, month, day, hour, minutes = [int(m) for m in result.groups()] 138 | return OrgDateTime(False, year, month, day, hour, minutes) 139 | except Exception: 140 | return None 141 | 142 | # handle passive dates 143 | result = _DATE_PASSIVE_REGEX.search(string) 144 | if result: 145 | try: 146 | year, month, day = [int(m) for m in result.groups()] 147 | return OrgDate(False, year, month, day) 148 | except Exception: 149 | return None 150 | 151 | # handle active dates 152 | result = _DATE_REGEX.search(string) 153 | if result: 154 | try: 155 | year, month, day = [int(m) for m in result.groups()] 156 | return OrgDate(True, year, month, day) 157 | except Exception: 158 | return None 159 | 160 | 161 | class OrgDate(datetime.date): 162 | u""" 163 | OrgDate represents a normal date like '2011-08-29 Mon'. 164 | 165 | OrgDates can be active or inactive. 166 | 167 | NOTE: date is immutable. Thats why there needs to be __new__(). 168 | See: http://docs.python.org/reference/datamodel.html#object.__new__ 169 | """ 170 | def __init__(self, active, year, month, day): 171 | self.active = active 172 | pass 173 | 174 | def __new__(cls, active, year, month, day): 175 | return datetime.date.__new__(cls, year, month, day) 176 | 177 | def __unicode__(self): 178 | u""" 179 | Return a string representation. 180 | """ 181 | if self.active: 182 | return self.strftime(u'<%Y-%m-%d %a>') 183 | else: 184 | return self.strftime(u'[%Y-%m-%d %a]') 185 | 186 | def __str__(self): 187 | return self.__unicode__().encode(u'utf-8') 188 | 189 | 190 | class OrgDateTime(datetime.datetime): 191 | u""" 192 | OrgDateTime represents a normal date like '2011-08-29 Mon'. 193 | 194 | OrgDateTime can be active or inactive. 195 | 196 | NOTE: date is immutable. Thats why there needs to be __new__(). 197 | See: http://docs.python.org/reference/datamodel.html#object.__new__ 198 | """ 199 | 200 | def __init__(self, active, year, month, day, hour, mins): 201 | self.active = active 202 | 203 | def __new__(cls, active, year, month, day, hour, minute): 204 | return datetime.datetime.__new__(cls, year, month, day, hour, minute) 205 | 206 | def __unicode__(self): 207 | u""" 208 | Return a string representation. 209 | """ 210 | if self.active: 211 | return self.strftime(u'<%Y-%m-%d %a %H:%M>') 212 | else: 213 | return self.strftime(u'[%Y-%m-%d %a %H:%M]') 214 | 215 | def __str__(self): 216 | return self.__unicode__().encode(u'utf-8') 217 | 218 | 219 | class OrgTimeRange(object): 220 | u""" 221 | OrgTimeRange objects have a start and an end. Start and ent can be date 222 | or datetime. Start and end have to be the same type. 223 | 224 | OrgTimeRange objects look like this: 225 | * <2011-09-07 Wed>--<2011-09-08 Fri> 226 | * <2011-09-07 Wed 20:00>--<2011-09-08 Fri 10:00> 227 | * <2011-09-07 Wed 10:00-13:00> 228 | """ 229 | 230 | def __init__(self, active, start, end): 231 | u""" 232 | stat and end must be datetime.date or datetime.datetime (both of the 233 | same type). 234 | """ 235 | super(OrgTimeRange, self).__init__() 236 | self.start = start 237 | self.end = end 238 | self.active = active 239 | 240 | def __unicode__(self): 241 | u""" 242 | Return a string representation. 243 | """ 244 | # active 245 | if self.active: 246 | # datetime 247 | if isinstance(self.start, datetime.datetime): 248 | # if start and end are on same the day 249 | if self.start.year == self.end.year and\ 250 | self.start.month == self.end.month and\ 251 | self.start.day == self.end.day: 252 | return u"<%s-%s>" % ( 253 | self.start.strftime(u'%Y-%m-%d %a %H:%M'), 254 | self.end.strftime(u'%H:%M')) 255 | else: 256 | return u"<%s>--<%s>" % ( 257 | self.start.strftime(u'%Y-%m-%d %a %H:%M'), 258 | self.end.strftime(u'%Y-%m-%d %a %H:%M')) 259 | # date 260 | if isinstance(self.start, datetime.date): 261 | return u"<%s>--<%s>" % ( 262 | self.start.strftime(u'%Y-%m-%d %a'), 263 | self.end.strftime(u'%Y-%m-%d %a')) 264 | # inactive 265 | else: 266 | if isinstance(self.start, datetime.datetime): 267 | # if start and end are on same the day 268 | if self.start.year == self.end.year and\ 269 | self.start.month == self.end.month and\ 270 | self.start.day == self.end.day: 271 | return u"[%s-%s]" % ( 272 | self.start.strftime(u'%Y-%m-%d %a %H:%M'), 273 | self.end.strftime(u'%H:%M')) 274 | else: 275 | return u"[%s]--[%s]" % ( 276 | self.start.strftime(u'%Y-%m-%d %a %H:%M'), 277 | self.end.strftime(u'%Y-%m-%d %a %H:%M')) 278 | if isinstance(self.start, datetime.date): 279 | return u"[%s]--[%s]" % ( 280 | self.start.strftime(u'%Y-%m-%d %a'), 281 | self.end.strftime(u'%Y-%m-%d %a')) 282 | 283 | def __str__(self): 284 | return self.__unicode__().encode(u'utf-8') 285 | 286 | # vim: set noexpandtab: 287 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/EditCheckbox.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | from orgmode._vim import echo, echom, echoe, ORGMODE, apply_count, repeat, insert_at_cursor, indent_orgmode 5 | from orgmode.menu import Submenu, Separator, ActionEntry, add_cmd_mapping_menu 6 | from orgmode.keybinding import Keybinding, Plug, Command 7 | from orgmode.liborgmode.checkboxes import Checkbox 8 | from orgmode.liborgmode.dom_obj import OrderListType 9 | 10 | 11 | class EditCheckbox(object): 12 | u""" 13 | Checkbox plugin. 14 | """ 15 | 16 | def __init__(self): 17 | u""" Initialize plugin """ 18 | object.__init__(self) 19 | # menu entries this plugin should create 20 | self.menu = ORGMODE.orgmenu + Submenu(u'Edit Checkbox') 21 | 22 | # key bindings for this plugin 23 | # key bindings are also registered through the menu so only additional 24 | # bindings should be put in this variable 25 | self.keybindings = [] 26 | 27 | # commands for this plugin 28 | self.commands = [] 29 | 30 | @classmethod 31 | def new_checkbox(cls, below=None): 32 | d = ORGMODE.get_document() 33 | h = d.current_heading() 34 | if h is None: 35 | return 36 | # init checkboxes for current heading 37 | h.init_checkboxes() 38 | c = h.current_checkbox() 39 | 40 | nc = Checkbox() 41 | nc._heading = h 42 | 43 | # default checkbox level 44 | level = h.level + 1 45 | start = vim.current.window.cursor[0] - 1 46 | # if no checkbox is found, insert at current line with indent level=1 47 | if c is None: 48 | h.checkboxes.append(nc) 49 | else: 50 | l = c.get_parent_list() 51 | idx = c.get_index_in_parent_list() 52 | if l is not None and idx is not None: 53 | l.insert(idx + (1 if below else 0), nc) 54 | # workaround for broken associations, Issue #165 55 | nc._parent = c.parent 56 | if below: 57 | if c.next_sibling: 58 | c.next_sibling._previous_sibling = nc 59 | nc._next_sibling = c.next_sibling 60 | c._next_sibling = nc 61 | nc._previous_sibling = c 62 | else: 63 | if c.previous_sibling: 64 | c.previous_sibling._next_sibling = nc 65 | nc._next_sibling = c 66 | nc._previous_sibling = c.previous_sibling 67 | c._previous_sibling = nc 68 | 69 | t = c.type 70 | # increase key for ordered lists 71 | if t[-1] in OrderListType: 72 | try: 73 | num = int(t[:-1]) + (1 if below else -1) 74 | if num < 0: 75 | # don't decrease to numbers below zero 76 | echom(u"Can't decrement further than '0'") 77 | return 78 | t = '%d%s' % (num, t[-1]) 79 | except ValueError: 80 | try: 81 | char = ord(t[:-1]) + (1 if below else -1) 82 | if below: 83 | if char == 91: 84 | # stop incrementing at Z (90) 85 | echom(u"Can't increment further than 'Z'") 86 | return 87 | elif char == 123: 88 | # increment from z (122) to A 89 | char = 65 90 | else: 91 | if char == 96: 92 | # stop decrementing at a (97) 93 | echom(u"Can't decrement further than 'a'") 94 | return 95 | elif char == 64: 96 | # decrement from A (65) to z 97 | char = 122 98 | t = u'%s%s' % (chr(char), t[-1]) 99 | except ValueError: 100 | pass 101 | nc.type = t 102 | if not c.status: 103 | nc.status = None 104 | level = c.level 105 | 106 | if below: 107 | start = c.end_of_last_child 108 | else: 109 | start = c.start 110 | nc.level = level 111 | 112 | if below: 113 | start += 1 114 | # vim's buffer behave just opposite to Python's list when inserting a 115 | # new item. The new entry is appended in vim put prepended in Python! 116 | vim.current.buffer[start:start] = [unicode(nc)] 117 | 118 | # update checkboxes status 119 | cls.update_checkboxes_status() 120 | 121 | vim.command((u'exe "normal %dgg"|startinsert!' % (start + 1, )).encode(u'utf-8')) 122 | 123 | @classmethod 124 | def toggle(cls, checkbox=None): 125 | u""" 126 | Toggle the checkbox given in the parameter. 127 | If the checkbox is not given, it will toggle the current checkbox. 128 | """ 129 | d = ORGMODE.get_document() 130 | current_heading = d.current_heading() 131 | # init checkboxes for current heading 132 | if current_heading is None: 133 | return 134 | current_heading = current_heading.init_checkboxes() 135 | 136 | if checkbox is None: 137 | # get current_checkbox 138 | c = current_heading.current_checkbox() 139 | # no checkbox found 140 | if c is None: 141 | cls.update_checkboxes_status() 142 | return 143 | else: 144 | c = checkbox 145 | 146 | if c.status == Checkbox.STATUS_OFF or c.status is None: 147 | # set checkbox status on if all children are on 148 | if not c.children or c.are_children_all(Checkbox.STATUS_ON): 149 | c.toggle() 150 | d.write_checkbox(c) 151 | elif c.status is None: 152 | c.status = Checkbox.STATUS_OFF 153 | d.write_checkbox(c) 154 | 155 | elif c.status == Checkbox.STATUS_ON: 156 | if not c.children or c.is_child_one(Checkbox.STATUS_OFF): 157 | c.toggle() 158 | d.write_checkbox(c) 159 | 160 | elif c.status == Checkbox.STATUS_INT: 161 | # can't toggle intermediate state directly according to emacs orgmode 162 | pass 163 | # update checkboxes status 164 | cls.update_checkboxes_status() 165 | 166 | @classmethod 167 | def _update_subtasks(cls): 168 | d = ORGMODE.get_document() 169 | h = d.current_heading() 170 | # init checkboxes for current heading 171 | h.init_checkboxes() 172 | # update heading subtask info 173 | c = h.first_checkbox 174 | if c is None: 175 | return 176 | total, on = c.all_siblings_status() 177 | h.update_subtasks(total, on) 178 | # update all checkboxes under current heading 179 | cls._update_checkboxes_subtasks(c) 180 | 181 | @classmethod 182 | def _update_checkboxes_subtasks(cls, checkbox): 183 | # update checkboxes 184 | for c in checkbox.all_siblings(): 185 | if c.children: 186 | total, on = c.first_child.all_siblings_status() 187 | c.update_subtasks(total, on) 188 | cls._update_checkboxes_subtasks(c.first_child) 189 | 190 | @classmethod 191 | def update_checkboxes_status(cls): 192 | d = ORGMODE.get_document() 193 | h = d.current_heading() 194 | if h is None: 195 | return 196 | # init checkboxes for current heading 197 | h.init_checkboxes() 198 | 199 | cls._update_checkboxes_status(h.first_checkbox) 200 | cls._update_subtasks() 201 | 202 | @classmethod 203 | def _update_checkboxes_status(cls, checkbox=None): 204 | u""" helper function for update checkboxes status 205 | :checkbox: The first checkbox of this indent level 206 | :return: The status of the parent checkbox 207 | """ 208 | if checkbox is None: 209 | return 210 | 211 | status_off, status_on, status_int, total = 0, 0, 0, 0 212 | # update all top level checkboxes' status 213 | for c in checkbox.all_siblings(): 214 | current_status = c.status 215 | # if this checkbox is not leaf, its status should determine by all its children 216 | if c.children: 217 | current_status = cls._update_checkboxes_status(c.first_child) 218 | 219 | # don't update status if the checkbox has no status 220 | if c.status is None: 221 | current_status = None 222 | # the checkbox needs to have status 223 | else: 224 | total += 1 225 | 226 | # count number of status in this checkbox level 227 | if current_status == Checkbox.STATUS_OFF: 228 | status_off += 1 229 | elif current_status == Checkbox.STATUS_ON: 230 | status_on += 1 231 | elif current_status == Checkbox.STATUS_INT: 232 | status_int += 1 233 | 234 | # write status if any update 235 | if current_status is not None and c.status != current_status: 236 | c.status = current_status 237 | d = ORGMODE.get_document() 238 | d.write_checkbox(c) 239 | 240 | parent_status = Checkbox.STATUS_INT 241 | # all silbing checkboxes are off status 242 | if status_off == total: 243 | parent_status = Checkbox.STATUS_OFF 244 | # all silbing checkboxes are on status 245 | elif status_on == total: 246 | parent_status = Checkbox.STATUS_ON 247 | # one silbing checkbox is on or int status 248 | elif status_on != 0 or status_int != 0: 249 | parent_status = Checkbox.STATUS_INT 250 | # other cases 251 | else: 252 | parent_status = None 253 | 254 | return parent_status 255 | 256 | def register(self): 257 | u""" 258 | Registration of the plugin. 259 | 260 | Key bindings and other initialization should be done here. 261 | """ 262 | add_cmd_mapping_menu( 263 | self, 264 | name=u'OrgCheckBoxNewAbove', 265 | function=u':py ORGMODE.plugins[u"EditCheckbox"].new_checkbox()', 266 | key_mapping=u'cN', 267 | menu_desrc=u'New CheckBox Above' 268 | ) 269 | add_cmd_mapping_menu( 270 | self, 271 | name=u'OrgCheckBoxNewBelow', 272 | function=u':py ORGMODE.plugins[u"EditCheckbox"].new_checkbox(below=True)', 273 | key_mapping=u'cn', 274 | menu_desrc=u'New CheckBox Below' 275 | ) 276 | add_cmd_mapping_menu( 277 | self, 278 | name=u'OrgCheckBoxToggle', 279 | function=u':silent! py ORGMODE.plugins[u"EditCheckbox"].toggle()', 280 | key_mapping=u'cc', 281 | menu_desrc=u'Toggle Checkbox' 282 | ) 283 | add_cmd_mapping_menu( 284 | self, 285 | name=u'OrgCheckBoxUpdate', 286 | function=u':silent! py ORGMODE.plugins[u"EditCheckbox"].update_checkboxes_status()', 287 | key_mapping=u'c#', 288 | menu_desrc=u'Update Subtasks' 289 | ) 290 | 291 | # vim: set noexpandtab: 292 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/Agenda.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import date 4 | import os 5 | import glob 6 | 7 | import vim 8 | 9 | from orgmode._vim import ORGMODE, get_bufnumber, get_bufname, echoe 10 | from orgmode import settings 11 | from orgmode.keybinding import Keybinding, Plug, Command 12 | from orgmode.menu import Submenu, ActionEntry, add_cmd_mapping_menu 13 | 14 | 15 | class Agenda(object): 16 | u""" 17 | The Agenda Plugin uses liborgmode.agenda to display the agenda views. 18 | 19 | The main task is to format the agenda from liborgmode.agenda. 20 | Also all the mappings: jump from agenda to todo, etc are realized here. 21 | """ 22 | 23 | def __init__(self): 24 | u""" Initialize plugin """ 25 | object.__init__(self) 26 | # menu entries this plugin should create 27 | self.menu = ORGMODE.orgmenu + Submenu(u'Agenda') 28 | 29 | # key bindings for this plugin 30 | # key bindings are also registered through the menu so only additional 31 | # bindings should be put in this variable 32 | self.keybindings = [] 33 | 34 | # commands for this plugin 35 | self.commands = [] 36 | 37 | @classmethod 38 | def _switch_to(cls, bufname, vim_commands=None): 39 | u""" 40 | Swicht to the buffer with bufname. 41 | 42 | A list of vim.commands (if given) gets executed as well. 43 | 44 | TODO: this should be extracted and imporved to create an easy to use 45 | way to create buffers/jump to buffers. Otherwise there are going to be 46 | quite a few ways to open buffers in vimorgmode. 47 | """ 48 | cmds = [ 49 | u'botright split org:%s' % bufname, 50 | u'setlocal buftype=nofile', 51 | u'setlocal modifiable', 52 | u'setlocal nonumber', 53 | # call opendoc() on enter the original todo item 54 | u'nnoremap :exec "py ORGMODE.plugins[u\'Agenda\'].opendoc()"', 55 | u'nnoremap :exec "py ORGMODE.plugins[u\'Agenda\'].opendoc(switch=True)"', 56 | u'nnoremap :exec "py ORGMODE.plugins[u\'Agenda\'].opendoc(split=True)"', 57 | # statusline 58 | u'setlocal statusline=Org\\ %s' % bufname] 59 | if vim_commands: 60 | cmds.extend(vim_commands) 61 | for cmd in cmds: 62 | vim.command(cmd.encode(u'utf-8')) 63 | 64 | @classmethod 65 | def _get_agendadocuments(self): 66 | u""" 67 | Return the org documents of the agenda files; return None if no 68 | agenda documents are defined. 69 | 70 | TODO: maybe turn this into an decorator? 71 | """ 72 | # load org files of agenda 73 | agenda_files = settings.get(u'org_agenda_files', u',') 74 | if not agenda_files or agenda_files == ',': 75 | echoe(( 76 | u"No org_agenda_files defined. Use :let " 77 | u"g:org_agenda_files=['~/org/index.org'] to add " 78 | u"files to the agenda view.")) 79 | return 80 | 81 | # glob for files in agenda_files 82 | resolved_files = [] 83 | for f in agenda_files: 84 | f = glob.glob(os.path.join( 85 | os.path.expanduser(os.path.dirname(f)), 86 | os.path.basename(f))) 87 | resolved_files.extend(f) 88 | 89 | agenda_files = [os.path.realpath(f) for f in resolved_files] 90 | 91 | # load the agenda files into buffers 92 | for agenda_file in agenda_files: 93 | vim.command((u'badd %s' % agenda_file.replace(" ", "\ ")).encode(u'utf-8')) 94 | 95 | # determine the buffer nr of the agenda files 96 | agenda_nums = [get_bufnumber(fn) for fn in agenda_files] 97 | 98 | # collect all documents of the agenda files and create the agenda 99 | return [ORGMODE.get_document(i) for i in agenda_nums if i is not None] 100 | 101 | @classmethod 102 | def opendoc(cls, split=False, switch=False): 103 | u""" 104 | If you are in the agenda view jump to the document the item in the 105 | current line belongs to. cls.line2doc is used for that. 106 | 107 | :split: if True, open the document in a new split window. 108 | :switch: if True, switch to another window and open the the document 109 | there. 110 | """ 111 | row, _ = vim.current.window.cursor 112 | try: 113 | bufname, bufnr, destrow = cls.line2doc[row] 114 | except: 115 | return 116 | 117 | # reload source file if it is not loaded 118 | if get_bufname(bufnr) is None: 119 | vim.command((u'badd %s' % bufname).encode(u'utf-8')) 120 | bufnr = get_bufnumber(bufname) 121 | tmp = cls.line2doc[row] 122 | cls.line2doc[bufnr] = tmp 123 | # delete old endry 124 | del cls.line2doc[row] 125 | 126 | if split: 127 | vim.command((u"sbuffer %s" % bufnr).encode(u'utf-8')) 128 | elif switch: 129 | vim.command(u"wincmd w".encode(u'utf-8')) 130 | vim.command((u"buffer %d" % bufnr).encode(u'utf-8')) 131 | else: 132 | vim.command((u"buffer %s" % bufnr).encode(u'utf-8')) 133 | vim.command((u"normal! %dgg " % (destrow + 1)).encode(u'utf-8')) 134 | 135 | @classmethod 136 | def list_next_week(cls): 137 | agenda_documents = cls._get_agendadocuments() 138 | if not agenda_documents: 139 | return 140 | raw_agenda = ORGMODE.agenda_manager.get_next_week_and_active_todo( 141 | agenda_documents) 142 | 143 | # create buffer at bottom 144 | cmd = [u'setlocal filetype=orgagenda', ] 145 | cls._switch_to(u'AGENDA', cmd) 146 | 147 | # line2doc is a dic with the mapping: 148 | # line in agenda buffer --> source document 149 | # It's easy to jump to the right document this way 150 | cls.line2doc = {} 151 | # format text for agenda 152 | last_date = raw_agenda[0].active_date 153 | final_agenda = [u'Week Agenda:', unicode(last_date)] 154 | for i, h in enumerate(raw_agenda): 155 | # insert date information for every new date (not datetime) 156 | if unicode(h.active_date)[1:11] != unicode(last_date)[1:11]: 157 | today = date.today() 158 | # insert additional "TODAY" string 159 | if h.active_date.year == today.year and \ 160 | h.active_date.month == today.month and \ 161 | h.active_date.day == today.day: 162 | section = unicode(h.active_date) + u" TODAY" 163 | today_row = len(final_agenda) + 1 164 | else: 165 | section = unicode(h.active_date) 166 | final_agenda.append(section) 167 | 168 | # update last_date 169 | last_date = h.active_date 170 | 171 | bufname = os.path.basename(vim.buffers[h.document.bufnr].name) 172 | bufname = bufname[:-4] if bufname.endswith(u'.org') else bufname 173 | formated = u" %(bufname)s (%(bufnr)d) %(todo)s %(title)s" % { 174 | 'bufname': bufname, 175 | 'bufnr': h.document.bufnr, 176 | 'todo': h.todo, 177 | 'title': h.title 178 | } 179 | final_agenda.append(formated) 180 | cls.line2doc[len(final_agenda)] = (get_bufname(h.document.bufnr), h.document.bufnr, h.start) 181 | 182 | # show agenda 183 | vim.current.buffer[:] = [i.encode(u'utf-8') for i in final_agenda] 184 | vim.command(u'setlocal nomodifiable conceallevel=2 concealcursor=nc'.encode(u'utf-8')) 185 | # try to jump to the positon of today 186 | try: 187 | vim.command((u'normal! %sgg' % today_row).encode(u'utf-8')) 188 | except: 189 | pass 190 | 191 | @classmethod 192 | def list_all_todos(cls): 193 | u""" 194 | List all todos in all agenda files in one buffer. 195 | """ 196 | agenda_documents = cls._get_agendadocuments() 197 | if not agenda_documents: 198 | return 199 | raw_agenda = ORGMODE.agenda_manager.get_todo(agenda_documents) 200 | 201 | cls.line2doc = {} 202 | # create buffer at bottom 203 | cmd = [u'setlocal filetype=orgagenda'] 204 | cls._switch_to(u'AGENDA', cmd) 205 | 206 | # format text of agenda 207 | final_agenda = [] 208 | for i, h in enumerate(raw_agenda): 209 | tmp = u"%s %s" % (h.todo, h.title) 210 | final_agenda.append(tmp) 211 | cls.line2doc[len(final_agenda)] = (get_bufname(h.document.bufnr), h.document.bufnr, h.start) 212 | 213 | # show agenda 214 | vim.current.buffer[:] = [i.encode(u'utf-8') for i in final_agenda] 215 | vim.command(u'setlocal nomodifiable conceallevel=2 concealcursor=nc'.encode(u'utf-8')) 216 | 217 | @classmethod 218 | def list_timeline(cls): 219 | """ 220 | List a timeline of the current buffer to get an overview of the 221 | current file. 222 | """ 223 | raw_agenda = ORGMODE.agenda_manager.get_timestamped_items( 224 | [ORGMODE.get_document()]) 225 | 226 | # create buffer at bottom 227 | cmd = [u'setlocal filetype=orgagenda'] 228 | cls._switch_to(u'AGENDA', cmd) 229 | 230 | cls.line2doc = {} 231 | # format text of agenda 232 | final_agenda = [] 233 | for i, h in enumerate(raw_agenda): 234 | tmp = u"%s %s" % (h.todo, h.title) 235 | final_agenda.append(tmp) 236 | cls.line2doc[len(final_agenda)] = (get_bufname(h.document.bufnr), h.document.bufnr, h.start) 237 | 238 | # show agenda 239 | vim.current.buffer[:] = [i.encode(u'utf-8') for i in final_agenda] 240 | vim.command(u'setlocal nomodifiable conceallevel=2 concealcursor=nc'.encode(u'utf-8')) 241 | 242 | def register(self): 243 | u""" 244 | Registration of the plugin. 245 | 246 | Key bindings and other initialization should be done here. 247 | """ 248 | add_cmd_mapping_menu( 249 | self, 250 | name=u"OrgAgendaTodo", 251 | function=u':py ORGMODE.plugins[u"Agenda"].list_all_todos()', 252 | key_mapping=u'cat', 253 | menu_desrc=u'Agenda for all TODOs' 254 | ) 255 | add_cmd_mapping_menu( 256 | self, 257 | name=u"OrgAgendaWeek", 258 | function=u':py ORGMODE.plugins[u"Agenda"].list_next_week()', 259 | key_mapping=u'caa', 260 | menu_desrc=u'Agenda for the week' 261 | ) 262 | add_cmd_mapping_menu( 263 | self, 264 | name=u'OrgAgendaTimeline', 265 | function=u':py ORGMODE.plugins[u"Agenda"].list_timeline()', 266 | key_mapping=u'caL', 267 | menu_desrc=u'Timeline for this buffer' 268 | ) 269 | 270 | # vim: set noexpandtab: 271 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/Date.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | from datetime import timedelta, date, datetime 4 | 5 | import vim 6 | 7 | from orgmode._vim import ORGMODE, echom, insert_at_cursor, get_user_input 8 | from orgmode import settings 9 | from orgmode.keybinding import Keybinding, Plug 10 | from orgmode.menu import Submenu, ActionEntry, add_cmd_mapping_menu 11 | 12 | 13 | class Date(object): 14 | u""" 15 | Handles all date and timestamp related tasks. 16 | 17 | TODO: extend functionality (calendar, repetitions, ranges). See 18 | http://orgmode.org/guide/Dates-and-Times.html#Dates-and-Times 19 | """ 20 | 21 | date_regex = r"\d\d\d\d-\d\d-\d\d" 22 | datetime_regex = r"[A-Z]\w\w \d\d\d\d-\d\d-\d\d \d\d:\d\d>" 23 | 24 | month_mapping = { 25 | u'jan': 1, u'feb': 2, u'mar': 3, u'apr': 4, u'may': 5, 26 | u'jun': 6, u'jul': 7, u'aug': 8, u'sep': 9, u'oct': 10, u'nov': 11, 27 | u'dec': 12} 28 | 29 | def __init__(self): 30 | u""" Initialize plugin """ 31 | object.__init__(self) 32 | # menu entries this plugin should create 33 | self.menu = ORGMODE.orgmenu + Submenu(u'Dates and Scheduling') 34 | 35 | # key bindings for this plugin 36 | # key bindings are also registered through the menu so only additional 37 | # bindings should be put in this variable 38 | self.keybindings = [] 39 | 40 | # commands for this plugin 41 | self.commands = [] 42 | 43 | # set speeddating format that is compatible with orgmode 44 | try: 45 | if int(vim.eval(u'exists(":SpeedDatingFormat")'.encode(u'utf-8'))) == 2: 46 | vim.command(u':1SpeedDatingFormat %Y-%m-%d %a'.encode(u'utf-8')) 47 | vim.command(u':1SpeedDatingFormat %Y-%m-%d %a %H:%M'.encode(u'utf-8')) 48 | else: 49 | echom(u'Speeddating plugin not installed. Please install it.') 50 | except: 51 | echom(u'Speeddating plugin not installed. Please install it.') 52 | 53 | @classmethod 54 | def _modify_time(cls, startdate, modifier): 55 | u"""Modify the given startdate according to modifier. Return the new 56 | date or datetime. 57 | 58 | See http://orgmode.org/manual/The-date_002ftime-prompt.html 59 | """ 60 | if modifier is None or modifier == '' or modifier == '.': 61 | return startdate 62 | 63 | # rm crap from modifier 64 | modifier = modifier.strip() 65 | 66 | # check real date 67 | date_regex = r"(\d\d\d\d)-(\d\d)-(\d\d)" 68 | match = re.search(date_regex, modifier) 69 | if match: 70 | year, month, day = match.groups() 71 | newdate = date(int(year), int(month), int(day)) 72 | 73 | # check abbreviated date, seperated with '-' 74 | date_regex = u"(\d{1,2})-(\d+)-(\d+)" 75 | match = re.search(date_regex, modifier) 76 | if match: 77 | year, month, day = match.groups() 78 | newdate = date(2000 + int(year), int(month), int(day)) 79 | 80 | # check abbreviated date, seperated with '/' 81 | # month/day 82 | date_regex = u"(\d{1,2})/(\d{1,2})" 83 | match = re.search(date_regex, modifier) 84 | if match: 85 | month, day = match.groups() 86 | newdate = date(startdate.year, int(month), int(day)) 87 | # date should be always in the future 88 | if newdate < startdate: 89 | newdate = date(startdate.year + 1, int(month), int(day)) 90 | 91 | # check full date, seperated with 'space' 92 | # month day year 93 | # 'sep 12 9' --> 2009 9 12 94 | date_regex = u"(\w\w\w) (\d{1,2}) (\d{1,2})" 95 | match = re.search(date_regex, modifier) 96 | if match: 97 | gr = match.groups() 98 | day = int(gr[1]) 99 | month = int(cls.month_mapping[gr[0]]) 100 | year = 2000 + int(gr[2]) 101 | newdate = date(year, int(month), int(day)) 102 | 103 | # check days as integers 104 | date_regex = u"^(\d{1,2})$" 105 | match = re.search(date_regex, modifier) 106 | if match: 107 | newday, = match.groups() 108 | newday = int(newday) 109 | if newday > startdate.day: 110 | newdate = date(startdate.year, startdate.month, newday) 111 | else: 112 | # TODO: DIRTY, fix this 113 | # this does NOT cover all edge cases 114 | newdate = startdate + timedelta(days=28) 115 | newdate = date(newdate.year, newdate.month, newday) 116 | 117 | # check for full days: Mon, Tue, Wed, Thu, Fri, Sat, Sun 118 | modifier_lc = modifier.lower() 119 | match = re.search(u'mon|tue|wed|thu|fri|sat|sun', modifier_lc) 120 | if match: 121 | weekday_mapping = { 122 | u'mon': 0, u'tue': 1, u'wed': 2, u'thu': 3, 123 | u'fri': 4, u'sat': 5, u'sun': 6} 124 | diff = (weekday_mapping[modifier_lc] - startdate.weekday()) % 7 125 | # use next weeks weekday if current weekday is the same as modifier 126 | if diff == 0: 127 | diff = 7 128 | newdate = startdate + timedelta(days=diff) 129 | 130 | # check for days modifier with appended d 131 | match = re.search(u'\+(\d*)d', modifier) 132 | if match: 133 | days = int(match.groups()[0]) 134 | newdate = startdate + timedelta(days=days) 135 | 136 | # check for days modifier without appended d 137 | match = re.search(u'\+(\d*) |\+(\d*)$', modifier) 138 | if match: 139 | try: 140 | days = int(match.groups()[0]) 141 | except: 142 | days = int(match.groups()[1]) 143 | newdate = startdate + timedelta(days=days) 144 | 145 | # check for week modifier 146 | match = re.search(u'\+(\d+)w', modifier) 147 | if match: 148 | weeks = int(match.groups()[0]) 149 | newdate = startdate + timedelta(weeks=weeks) 150 | 151 | # check for week modifier 152 | match = re.search(u'\+(\d+)m', modifier) 153 | if match: 154 | months = int(match.groups()[0]) 155 | newdate = date(startdate.year, startdate.month + months, startdate.day) 156 | 157 | # check for year modifier 158 | match = re.search(u'\+(\d*)y', modifier) 159 | if match: 160 | years = int(match.groups()[0]) 161 | newdate = date(startdate.year + years, startdate.month, startdate.day) 162 | 163 | # check for month day 164 | match = re.search( 165 | u'(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) (\d{1,2})', 166 | modifier.lower()) 167 | if match: 168 | month = cls.month_mapping[match.groups()[0]] 169 | day = int(match.groups()[1]) 170 | newdate = date(startdate.year, int(month), int(day)) 171 | # date should be always in the future 172 | if newdate < startdate: 173 | newdate = date(startdate.year + 1, int(month), int(day)) 174 | 175 | # check abbreviated date, seperated with '/' 176 | # month/day/year 177 | date_regex = u"(\d{1,2})/(\d+)/(\d+)" 178 | match = re.search(date_regex, modifier) 179 | if match: 180 | month, day, year = match.groups() 181 | newdate = date(2000 + int(year), int(month), int(day)) 182 | 183 | # check for month day year 184 | # sep 12 2011 185 | match = re.search( 186 | u'(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) (\d{1,2}) (\d{1,4})', 187 | modifier.lower()) 188 | if match: 189 | month = int(cls.month_mapping[match.groups()[0]]) 190 | day = int(match.groups()[1]) 191 | if len(match.groups()[2]) < 4: 192 | year = 2000 + int(match.groups()[2]) 193 | else: 194 | year = int(match.groups()[2]) 195 | newdate = date(year, month, day) 196 | 197 | # check for time: HH:MM 198 | # '12:45' --> datetime(2006, 06, 13, 12, 45)) 199 | match = re.search(u'(\d{1,2}):(\d\d)$', modifier) 200 | if match: 201 | try: 202 | startdate = newdate 203 | except: 204 | pass 205 | return datetime( 206 | startdate.year, startdate.month, startdate.day, 207 | int(match.groups()[0]), int(match.groups()[1])) 208 | 209 | try: 210 | return newdate 211 | except: 212 | return startdate 213 | 214 | @classmethod 215 | def insert_timestamp(cls, active=True): 216 | u""" 217 | Insert a timestamp at the cursor position. 218 | 219 | TODO: show fancy calendar to pick the date from. 220 | TODO: add all modifier of orgmode. 221 | """ 222 | today = date.today() 223 | msg = u''.join([ 224 | u'Inserting ', 225 | unicode(today.strftime(u'%Y-%m-%d %a'), u'utf-8'), 226 | u' | Modify date']) 227 | modifier = get_user_input(msg) 228 | 229 | # abort if the user canceled the input promt 230 | if modifier is None: 231 | return 232 | 233 | newdate = cls._modify_time(today, modifier) 234 | 235 | # format 236 | if isinstance(newdate, datetime): 237 | newdate = newdate.strftime( 238 | u'%Y-%m-%d %a %H:%M'.encode(u'utf-8')).decode(u'utf-8') 239 | else: 240 | newdate = newdate.strftime( 241 | u'%Y-%m-%d %a'.encode(u'utf-8')).decode(u'utf-8') 242 | timestamp = u'<%s>' % newdate if active else u'[%s]' % newdate 243 | 244 | insert_at_cursor(timestamp) 245 | 246 | @classmethod 247 | def insert_timestamp_with_calendar(cls, active=True): 248 | u""" 249 | Insert a timestamp at the cursor position. 250 | Show fancy calendar to pick the date from. 251 | 252 | TODO: add all modifier of orgmode. 253 | """ 254 | if int(vim.eval(u'exists(":CalendarH")'.encode(u'utf-8'))) != 2: 255 | vim.command("echo 'Please install plugin Calendar to enable this function'") 256 | return 257 | vim.command("CalendarH") 258 | # backup calendar_action 259 | calendar_action = vim.eval("g:calendar_action") 260 | vim.command("let g:org_calendar_action_backup = '" + calendar_action + "'") 261 | vim.command("let g:calendar_action = 'CalendarAction'") 262 | 263 | timestamp_template = u'<%s>' if active else u'[%s]' 264 | # timestamp template 265 | vim.command("let g:org_timestamp_template = '" + timestamp_template + "'") 266 | 267 | def register(self): 268 | u""" 269 | Registration of the plugin. 270 | 271 | Key bindings and other initialization should be done here. 272 | """ 273 | add_cmd_mapping_menu( 274 | self, 275 | name=u'OrgDateInsertTimestampActiveCmdLine', 276 | key_mapping=u'sa', 277 | function=u':py ORGMODE.plugins[u"Date"].insert_timestamp()', 278 | menu_desrc=u'Timest&' 279 | ) 280 | add_cmd_mapping_menu( 281 | self, 282 | name=u'OrgDateInsertTimestampInactiveCmdLine', 283 | key_mapping='si', 284 | function=u':py ORGMODE.plugins[u"Date"].insert_timestamp(False)', 285 | menu_desrc=u'Timestamp (&inactive)' 286 | ) 287 | add_cmd_mapping_menu( 288 | self, 289 | name=u'OrgDateInsertTimestampActiveWithCalendar', 290 | key_mapping=u'pa', 291 | function=u':py ORGMODE.plugins[u"Date"].insert_timestamp_with_calendar()', 292 | menu_desrc=u'Timestamp with Calendar' 293 | ) 294 | add_cmd_mapping_menu( 295 | self, 296 | name=u'OrgDateInsertTimestampInactiveWithCalendar', 297 | key_mapping=u'pi', 298 | function=u':py ORGMODE.plugins[u"Date"].insert_timestamp_with_calendar(False)', 299 | menu_desrc=u'Timestamp with Calendar(inactive)' 300 | ) 301 | 302 | submenu = self.menu + Submenu(u'Change &Date') 303 | submenu + ActionEntry(u'Day &Earlier', u'', u'') 304 | submenu + ActionEntry(u'Day &Later', u'', u'') 305 | 306 | # vim: set noexpandtab: 307 | -------------------------------------------------------------------------------- /ftplugin/orgmode/liborgmode/checkboxes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | checkboxes 5 | ~~~~~~~~~ 6 | 7 | TODO: explain this :) 8 | """ 9 | 10 | import re 11 | from UserList import UserList 12 | 13 | import vim 14 | from orgmode.liborgmode.base import MultiPurposeList, flatten_list 15 | from orgmode.liborgmode.orgdate import OrgTimeRange 16 | from orgmode.liborgmode.orgdate import get_orgdate 17 | from orgmode.liborgmode.dom_obj import DomObj, DomObjList, REGEX_SUBTASK, REGEX_SUBTASK_PERCENT, REGEX_HEADING, REGEX_CHECKBOX 18 | 19 | 20 | class Checkbox(DomObj): 21 | u""" Structural checkbox object """ 22 | STATUS_ON = u'[X]' 23 | STATUS_OFF = u'[ ]' 24 | # intermediate status 25 | STATUS_INT = u'[-]' 26 | 27 | def __init__(self, level=1, type=u'-', title=u'', status=u'[ ]', body=None): 28 | u""" 29 | :level: Indent level of the checkbox 30 | :type: Type of the checkbox list (-, +, *) 31 | :title: Title of the checkbox 32 | :status: Status of the checkbox ([ ], [X], [-]) 33 | :body: Body of the checkbox 34 | """ 35 | DomObj.__init__(self, level=level, title=title, body=body) 36 | 37 | # heading 38 | self._heading = None 39 | 40 | self._children = CheckboxList(obj=self) 41 | self._dirty_checkbox = False 42 | # list type 43 | self._type = u'-' 44 | if type: 45 | self.type = type 46 | # status 47 | self._status = Checkbox.STATUS_OFF 48 | if status: 49 | self.status = status 50 | 51 | def __unicode__(self): 52 | return u' ' * self.level + self.type + u' ' + \ 53 | (self.status + u' ' if self.status else u'') + self.title 54 | 55 | def __str__(self): 56 | return self.__unicode__().encode(u'utf-8') 57 | 58 | def __len__(self): 59 | # 1 is for the heading's title 60 | return 1 + len(self.body) 61 | 62 | def copy(self, including_children=True, parent=None): 63 | u""" 64 | Create a copy of the current checkbox. The checkbox will be completely 65 | detached and not even belong to a document anymore. 66 | 67 | :including_children: If True a copy of all children is create as 68 | well. If False the returned checkbox doesn't 69 | have any children. 70 | :parent: Don't use this parameter. It's set 71 | automatically. 72 | """ 73 | checkbox = self.__class__( 74 | level=self.level, title=self.title, 75 | body=self.body[:]) 76 | if parent: 77 | parent.children.append(checkbox) 78 | if including_children and self.children: 79 | for item in self.children: 80 | item.copy( 81 | including_children=including_children, 82 | parent=checkbox) 83 | checkbox._orig_start = self._orig_start 84 | checkbox._orig_len = self._orig_len 85 | 86 | checkbox._dirty_heading = self.is_dirty_checkbox 87 | 88 | return checkbox 89 | 90 | @classmethod 91 | def parse_checkbox_from_data(cls, data, heading=None, orig_start=None): 92 | u""" Construct a new checkbox from the provided data 93 | 94 | :data: List of lines 95 | :heading: The heading object this checkbox belongs to 96 | :orig_start: The original start of the heading in case it was read 97 | from a document. If orig_start is provided, the 98 | resulting heading will not be marked dirty. 99 | 100 | :returns: The newly created checkbox 101 | """ 102 | def parse_title(heading_line): 103 | # checkbox is not heading 104 | if REGEX_HEADING.match(heading_line) is not None: 105 | return None 106 | m = REGEX_CHECKBOX.match(heading_line) 107 | if m: 108 | r = m.groupdict() 109 | return (len(r[u'level']), r[u'type'], r[u'status'], r[u'title']) 110 | 111 | return None 112 | 113 | if not data: 114 | raise ValueError(u'Unable to create checkbox, no data provided.') 115 | 116 | # create new checkbox 117 | nc = cls() 118 | nc.level, nc.type, nc.status, nc.title = parse_title(data[0]) 119 | nc.body = data[1:] 120 | if orig_start is not None: 121 | nc._dirty_heading = False 122 | nc._dirty_body = False 123 | nc._orig_start = orig_start 124 | nc._orig_len = len(nc) 125 | if heading: 126 | nc._heading = heading 127 | 128 | return nc 129 | 130 | def update_subtasks(self, total=0, on=0): 131 | if total != 0: 132 | percent = (on * 100) / total 133 | else: 134 | percent = 0 135 | 136 | count = "%d/%d" % (on, total) 137 | self.title = REGEX_SUBTASK.sub("[%s]" % (count), self.title) 138 | self.title = REGEX_SUBTASK_PERCENT.sub("[%d%%]" % (percent), self.title) 139 | d = self._heading.document.write_checkbox(self, including_children=False) 140 | 141 | @classmethod 142 | def identify_checkbox(cls, line): 143 | u""" Test if a certain line is a checkbox or not. 144 | 145 | :line: the line to check 146 | 147 | :returns: indent_level 148 | """ 149 | # checkbox is not heading 150 | if REGEX_HEADING.match(line) is not None: 151 | return None 152 | m = REGEX_CHECKBOX.match(line) 153 | if m: 154 | r = m.groupdict() 155 | return len(r[u'level']) 156 | 157 | return None 158 | 159 | @property 160 | def is_dirty(self): 161 | u""" Return True if the heading's body is marked dirty """ 162 | return self._dirty_checkbox or self._dirty_body 163 | 164 | @property 165 | def is_dirty_checkbox(self): 166 | u""" Return True if the heading is marked dirty """ 167 | return self._dirty_checkbox 168 | 169 | def get_index_in_parent_list(self): 170 | """ Retrieve the index value of current checkbox in the parents list of 171 | checkboxes. This works also for top level checkboxes. 172 | 173 | :returns: Index value or None if heading doesn't have a 174 | parent/document or is not in the list of checkboxes 175 | """ 176 | if self.parent: 177 | return super(Checkbox, self).get_index_in_parent_list() 178 | elif self.document: 179 | l = self.get_parent_list() 180 | if l: 181 | return l.index(self) 182 | 183 | def get_parent_list(self): 184 | """ Retrieve the parents' list of headings. This works also for top 185 | level headings. 186 | 187 | :returns: List of headings or None if heading doesn't have a 188 | parent/document or is not in the list of headings 189 | """ 190 | if self.parent: 191 | return super(Checkbox, self).get_parent_list() 192 | elif self.document: 193 | if self in self.document.checkboxes: 194 | return self.document.checkboxes 195 | 196 | def set_dirty(self): 197 | u""" Mark the heading and body dirty so that it will be rewritten when 198 | saving the document """ 199 | self._dirty_checkbox = True 200 | self._dirty_body = True 201 | if self._document: 202 | self._document.set_dirty_document() 203 | 204 | def set_dirty_checkbox(self): 205 | u""" Mark the checkbox dirty so that it will be rewritten when saving the 206 | document """ 207 | self._dirty_checkbox = True 208 | if self._document: 209 | self._document.set_dirty_document() 210 | 211 | @property 212 | def previous_checkbox(self): 213 | u""" Serialized access to the previous checkbox """ 214 | return super(Checkbox, self).previous_item 215 | 216 | @property 217 | def next_checkbox(self): 218 | u""" Serialized access to the next checkbox """ 219 | return super(Checkbox, self).next_item 220 | 221 | @property 222 | def first_checkbox(self): 223 | u""" Access to the first child heading or None if no children exist """ 224 | if self.children: 225 | return self.children[0] 226 | 227 | @property 228 | def start(self): 229 | u""" Access to the starting line of the checkbox """ 230 | if self.document is None: 231 | return self._orig_start 232 | 233 | # static computation of start 234 | if not self.document.is_dirty: 235 | return self._orig_start 236 | 237 | # dynamic computation of start, really slow! 238 | def compute_start(h): 239 | if h: 240 | return len(h) + compute_start(h.previous_checkbox) 241 | return compute_start(self.previous_checkbox) 242 | 243 | def toggle(self): 244 | u""" Toggle status of this checkbox """ 245 | if self.status == Checkbox.STATUS_OFF or self.status is None: 246 | self.status = Checkbox.STATUS_ON 247 | else: 248 | self.status = Checkbox.STATUS_OFF 249 | self.set_dirty() 250 | 251 | def all_siblings(self): 252 | if not self.parent: 253 | p = self._heading 254 | else: 255 | p = self.parent 256 | if not p.children: 257 | raise StopIteration() 258 | 259 | c = p.first_checkbox 260 | while c: 261 | yield c 262 | c = c.next_sibling 263 | raise StopIteration() 264 | 265 | def all_children(self): 266 | if not self.children: 267 | raise StopIteration() 268 | 269 | c = self.first_checkbox 270 | while c: 271 | yield c 272 | for d in c.all_children(): 273 | yield d 274 | c = c.next_sibling 275 | 276 | raise StopIteration() 277 | 278 | def all_siblings_status(self): 279 | u""" Return checkboxes status for currnet checkbox's all siblings 280 | 281 | :return: (total, on) 282 | total: total # of checkboxes 283 | on: # of checkboxes which are on 284 | """ 285 | total, on = 0, 0 286 | for c in self.all_siblings(): 287 | if c.status is not None: 288 | total += 1 289 | 290 | if c.status == Checkbox.STATUS_ON: 291 | on += 1 292 | 293 | return (total, on) 294 | 295 | def are_children_all(self, status): 296 | u""" Check all children checkboxes status """ 297 | clen = len(self.children) 298 | for i in range(clen): 299 | if self.children[i].status != status: 300 | return False 301 | # recursively check children's status 302 | if not self.children[i].are_children_all(status): 303 | return False 304 | 305 | return True 306 | 307 | def is_child_one(self, status): 308 | u""" Return true, if there is one child with given status """ 309 | clen = len(self.children) 310 | for i in range(clen): 311 | if self.children[i].status == status: 312 | return True 313 | 314 | return False 315 | 316 | def are_siblings_all(self, status): 317 | u""" Check all sibling checkboxes status """ 318 | for c in self.all_siblings(): 319 | if c.status != status: 320 | return False 321 | 322 | return True 323 | 324 | def level(): 325 | u""" Access to the checkbox indent level """ 326 | def fget(self): 327 | return self._level 328 | 329 | def fset(self, value): 330 | self._level = int(value) 331 | self.set_dirty_checkbox() 332 | 333 | def fdel(self): 334 | self.level = None 335 | 336 | return locals() 337 | level = property(**level()) 338 | 339 | def title(): 340 | u""" Title of current checkbox """ 341 | def fget(self): 342 | return self._title.strip() 343 | 344 | def fset(self, value): 345 | if type(value) not in (unicode, str): 346 | raise ValueError(u'Title must be a string.') 347 | v = value 348 | if type(v) == str: 349 | v = v.decode(u'utf-8') 350 | self._title = v.strip() 351 | self.set_dirty_checkbox() 352 | 353 | def fdel(self): 354 | self.title = u'' 355 | 356 | return locals() 357 | title = property(**title()) 358 | 359 | def status(): 360 | u""" status of current checkbox """ 361 | def fget(self): 362 | return self._status 363 | 364 | def fset(self, value): 365 | self._status = value 366 | self.set_dirty() 367 | 368 | def fdel(self): 369 | self._status = u'' 370 | 371 | return locals() 372 | status = property(**status()) 373 | 374 | def type(): 375 | u""" type of current checkbox list type """ 376 | def fget(self): 377 | return self._type 378 | 379 | def fset(self, value): 380 | self._type = value 381 | 382 | def fdel(self): 383 | self._type = u'' 384 | 385 | return locals() 386 | type = property(**type()) 387 | 388 | 389 | class CheckboxList(DomObjList): 390 | u""" 391 | Checkbox List 392 | """ 393 | def __init__(self, initlist=None, obj=None): 394 | """ 395 | :initlist: Initial data 396 | :obj: Link to a concrete Checkbox or Document object 397 | """ 398 | # it's not necessary to register a on_change hook because the heading 399 | # list will itself take care of marking headings dirty or adding 400 | # headings to the deleted headings list 401 | DomObjList.__init__(self, initlist, obj) 402 | 403 | @classmethod 404 | def is_checkbox(cls, obj): 405 | return CheckboxList.is_domobj(obj) 406 | 407 | def _get_heading(self): 408 | if self.__class__.is_checkbox(self._obj): 409 | return self._obj._document 410 | return self._obj 411 | 412 | 413 | # vim: set noexpandtab: 414 | -------------------------------------------------------------------------------- /ftplugin/orgmode/_vim.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | VIM ORGMODE 5 | ~~~~~~~~~~~~ 6 | 7 | TODO 8 | """ 9 | 10 | import imp 11 | import types 12 | import re 13 | 14 | import vim 15 | from datetime import datetime 16 | 17 | import orgmode.keybinding 18 | import orgmode.menu 19 | import orgmode.plugins 20 | import orgmode.settings 21 | from orgmode.exceptions import PluginError 22 | from orgmode.vimbuffer import VimBuffer 23 | from orgmode.liborgmode.agenda import AgendaManager 24 | 25 | 26 | REPEAT_EXISTS = bool(int(vim.eval('exists("*repeat#set()")'))) 27 | TAGSPROPERTIES_EXISTS = False 28 | 29 | cache_heading = None 30 | 31 | def realign_tags(f): 32 | u""" 33 | Update tag alignment, dependency to TagsProperties plugin! 34 | """ 35 | def r(*args, **kwargs): 36 | global TAGSPROPERTIES_EXISTS 37 | res = f(*args, **kwargs) 38 | 39 | if not TAGSPROPERTIES_EXISTS and u'TagsProperties' in ORGMODE.plugins: 40 | TAGSPROPERTIES_EXISTS = True 41 | 42 | if TAGSPROPERTIES_EXISTS: 43 | ORGMODE.plugins[u'TagsProperties'].realign_tags() 44 | 45 | return res 46 | return r 47 | 48 | 49 | def repeat(f): 50 | u""" 51 | Integrate with the repeat plugin if available 52 | 53 | The decorated function must return the name of the command to 54 | execute by the repeat plugin. 55 | """ 56 | def r(*args, **kwargs): 57 | res = f(*args, **kwargs) 58 | if REPEAT_EXISTS and isinstance(res, basestring): 59 | vim.command((u'silent! call repeat#set("\\%s")' % res) 60 | .encode(u'utf-8')) 61 | return res 62 | return r 63 | 64 | 65 | def apply_count(f): 66 | u""" 67 | Decorator which executes function v:count or v:prevount (not implemented, 68 | yet) times. The decorated function must return a value that evaluates to 69 | True otherwise the function is not repeated. 70 | """ 71 | def r(*args, **kwargs): 72 | count = 0 73 | try: 74 | count = int(vim.eval(u'v:count'.encode('utf-8'))) 75 | 76 | # visual count is not implemented yet 77 | #if not count: 78 | # count = int(vim.eval(u'v:prevcount'.encode(u'utf-8'))) 79 | except Exception, e: 80 | pass 81 | 82 | res = f(*args, **kwargs) 83 | count -= 1 84 | while res and count > 0: 85 | f(*args, **kwargs) 86 | count -= 1 87 | return res 88 | return r 89 | 90 | 91 | def echo(message): 92 | u""" 93 | Print a regular message that will not be visible to the user when 94 | multiple lines are printed 95 | """ 96 | for m in message.split(u'\n'): 97 | vim.command((u':echo "%s"' % m).encode(u'utf-8')) 98 | 99 | 100 | def echom(message): 101 | u""" 102 | Print a regular message that will be visible to the user, even when 103 | multiple lines are printed 104 | """ 105 | # probably some escaping is needed here 106 | for m in message.split(u'\n'): 107 | vim.command((u':echomsg "%s"' % m).encode(u'utf-8')) 108 | 109 | 110 | def echoe(message): 111 | u""" 112 | Print an error message. This should only be used for serious errors! 113 | """ 114 | # probably some escaping is needed here 115 | for m in message.split(u'\n'): 116 | vim.command((u':echoerr "%s"' % m).encode(u'utf-8')) 117 | 118 | 119 | def insert_at_cursor(text, move=True, start_insertmode=False): 120 | u"""Insert text at the position of the cursor. 121 | 122 | If move==True move the cursor with the inserted text. 123 | """ 124 | d = ORGMODE.get_document(allow_dirty=True) 125 | line, col = vim.current.window.cursor 126 | _text = d._content[line - 1] 127 | d._content[line - 1] = _text[:col + 1] + text + _text[col + 1:] 128 | if move: 129 | vim.current.window.cursor = (line, col + len(text)) 130 | if start_insertmode: 131 | vim.command(u'startinsert'.encode(u'utf-8')) 132 | 133 | 134 | def get_user_input(message): 135 | u"""Print the message and take input from the user. 136 | Return the input or None if there is no input. 137 | """ 138 | vim.command(u'call inputsave()'.encode(u'utf-8')) 139 | vim.command((u"let user_input = input('" + message + u": ')") 140 | .encode(u'utf-8')) 141 | vim.command(u'call inputrestore()'.encode(u'utf-8')) 142 | try: 143 | return vim.eval(u'user_input'.encode(u'utf-8')).decode(u'utf-8') 144 | except: 145 | return None 146 | 147 | 148 | def get_bufnumber(bufname): 149 | """ 150 | Return the number of the buffer for the given bufname if it exist; 151 | else None. 152 | """ 153 | for b in vim.buffers: 154 | if b.name == bufname: 155 | return int(b.number) 156 | 157 | 158 | def get_bufname(bufnr): 159 | """ 160 | Return the name of the buffer for the given bufnr if it exist; else None. 161 | """ 162 | for b in vim.buffers: 163 | if b.number == bufnr: 164 | return b.name 165 | 166 | 167 | def indent_orgmode(): 168 | u""" Set the indent value for the current line in the variable 169 | b:indent_level 170 | 171 | Vim prerequisites: 172 | :setlocal indentexpr=Method-which-calls-indent_orgmode 173 | 174 | :returns: None 175 | """ 176 | line = int(vim.eval(u'v:lnum'.encode(u'utf-8'))) 177 | d = ORGMODE.get_document() 178 | heading = d.current_heading(line - 1) 179 | if heading and line != heading.start_vim: 180 | heading.init_checkboxes() 181 | checkbox = heading.current_checkbox() 182 | level = heading.level + 1 183 | if checkbox: 184 | level = level + checkbox.number_of_parents * 6 185 | if line != checkbox.start_vim: 186 | # indent body up to the beginning of the checkbox' text 187 | # if checkbox isn't indented to the proper location, the body 188 | # won't be indented either 189 | level = checkbox.level + len(checkbox.type) + 1 + \ 190 | (4 if checkbox.status else 0) 191 | vim.command((u'let b:indent_level = %d' % level).encode(u'utf-8')) 192 | 193 | 194 | def fold_text(allow_dirty=False): 195 | u""" Set the fold text 196 | :setlocal foldtext=Method-which-calls-foldtext 197 | 198 | :allow_dirty: Perform a query without (re)building the DOM if True 199 | :returns: None 200 | """ 201 | line = int(vim.eval(u'v:foldstart'.encode(u'utf-8'))) 202 | d = ORGMODE.get_document(allow_dirty=allow_dirty) 203 | heading = None 204 | if allow_dirty: 205 | heading = d.find_current_heading(line - 1) 206 | else: 207 | heading = d.current_heading(line - 1) 208 | if heading: 209 | str_heading = unicode(heading) 210 | 211 | # expand tabs 212 | ts = int(vim.eval(u'&ts'.encode('utf-8'))) 213 | idx = str_heading.find(u'\t') 214 | if idx != -1: 215 | tabs, spaces = divmod(idx, ts) 216 | str_heading = str_heading.replace(u'\t', u' ' * (ts - spaces), 1) 217 | str_heading = str_heading.replace(u'\t', u' ' * ts) 218 | 219 | # Workaround for vim.command seems to break the completion menu 220 | vim.eval((u'SetOrgFoldtext("%s...")' % (re.sub(r'\[\[([^[\]]*\]\[)?([^[\]]+)\]\]', r'\2', 221 | str_heading).replace( u'\\', u'\\\\').replace(u'"', u'\\"'), )).encode(u'utf-8')) 222 | 223 | 224 | def fold_orgmode(allow_dirty=False): 225 | u""" Set the fold expression/value for the current line in the variable 226 | b:fold_expr 227 | 228 | Vim prerequisites: 229 | :setlocal foldmethod=expr 230 | :setlocal foldexpr=Method-which-calls-fold_orgmode 231 | 232 | :allow_dirty: Perform a query without (re)building the DOM if True 233 | :returns: None 234 | """ 235 | line = int(vim.eval(u'v:lnum'.encode(u'utf-8'))) 236 | d = ORGMODE.get_document(allow_dirty=allow_dirty) 237 | heading = None 238 | if allow_dirty: 239 | heading = d.find_current_heading(line - 1) 240 | else: 241 | heading = d.current_heading(line - 1) 242 | 243 | # if cache_heading != heading: 244 | # heading.init_checkboxes() 245 | # checkbox = heading.current_checkbox() 246 | 247 | # cache_heading = heading 248 | if heading: 249 | # if checkbox: 250 | # vim.command((u'let b:fold_expr = ">%d"' % heading.level + checkbox.level).encode(u'utf-8')) 251 | if 0: 252 | pass 253 | elif line == heading.start_vim: 254 | vim.command((u'let b:fold_expr = ">%d"' % heading.level).encode(u'utf-8')) 255 | #elif line == heading.end_vim: 256 | # vim.command((u'let b:fold_expr = "<%d"' % heading.level).encode(u'utf-8')) 257 | # end_of_last_child_vim is a performance junky and is actually not needed 258 | #elif line == heading.end_of_last_child_vim: 259 | # vim.command((u'let b:fold_expr = "<%d"' % heading.level).encode(u'utf-8')) 260 | else: 261 | vim.command((u'let b:fold_expr = %d' % heading.level).encode(u'utf-8')) 262 | 263 | 264 | def date_to_str(date): 265 | if isinstance(date, datetime): 266 | date = date.strftime( 267 | u'%Y-%m-%d %a %H:%M'.encode(u'utf-8')).decode(u'utf-8') 268 | else: 269 | date = date.strftime( 270 | u'%Y-%m-%d %a'.encode(u'utf-8')).decode(u'utf-8') 271 | return date 272 | 273 | class OrgMode(object): 274 | u""" Vim Buffer """ 275 | 276 | def __init__(self): 277 | object.__init__(self) 278 | self.debug = bool(int(orgmode.settings.get(u'org_debug', False))) 279 | 280 | self.orgmenu = orgmode.menu.Submenu(u'&Org') 281 | self._plugins = {} 282 | # list of vim buffer objects 283 | self._documents = {} 284 | 285 | # agenda manager 286 | self.agenda_manager = AgendaManager() 287 | 288 | def get_document(self, bufnr=0, allow_dirty=False): 289 | """ Retrieve instance of vim buffer document. This Document should be 290 | used for manipulating the vim buffer. 291 | 292 | :bufnr: Retrieve document with bufnr 293 | :allow_dirty: Allow the retrieved document to be dirty 294 | 295 | :returns: vim buffer instance 296 | """ 297 | if bufnr == 0: 298 | bufnr = vim.current.buffer.number 299 | 300 | if bufnr in self._documents: 301 | if allow_dirty or self._documents[bufnr].is_insync: 302 | return self._documents[bufnr] 303 | self._documents[bufnr] = VimBuffer(bufnr).init_dom() 304 | return self._documents[bufnr] 305 | 306 | @property 307 | def plugins(self): 308 | return self._plugins.copy() 309 | 310 | @orgmode.keybinding.register_keybindings 311 | @orgmode.keybinding.register_commands 312 | @orgmode.menu.register_menu 313 | def register_plugin(self, plugin): 314 | if not isinstance(plugin, basestring): 315 | raise ValueError(u'Parameter plugin is not of type string') 316 | 317 | if plugin == u'|': 318 | self.orgmenu + orgmode.menu.Separator() 319 | self.orgmenu.children[-1].create() 320 | return 321 | 322 | if plugin in self._plugins: 323 | raise PluginError(u'Plugin %s has already been loaded') 324 | 325 | # a python module 326 | module = None 327 | 328 | # actual plugin class 329 | _class = None 330 | 331 | # locate module and initialize plugin class 332 | try: 333 | module = imp.find_module(plugin, orgmode.plugins.__path__) 334 | except ImportError, e: 335 | echom(u'Plugin not found: %s' % plugin) 336 | if self.debug: 337 | raise e 338 | return 339 | 340 | if not module: 341 | echom(u'Plugin not found: %s' % plugin) 342 | return 343 | 344 | try: 345 | module = imp.load_module(plugin, *module) 346 | if not hasattr(module, plugin): 347 | echoe(u'Unable to find plugin: %s' % plugin) 348 | if self.debug: 349 | raise PluginError(u'Unable to find class %s' % plugin) 350 | return 351 | _class = getattr(module, plugin) 352 | self._plugins[plugin] = _class() 353 | self._plugins[plugin].register() 354 | if self.debug: 355 | echo(u'Plugin registered: %s' % plugin) 356 | return self._plugins[plugin] 357 | except Exception, e: 358 | echoe(u'Unable to activate plugin: %s' % plugin) 359 | echoe(u"%s" % e) 360 | if self.debug: 361 | import traceback 362 | echoe(traceback.format_exc()) 363 | 364 | def register_keybindings(self): 365 | @orgmode.keybinding.register_keybindings 366 | def dummy(plugin): 367 | return plugin 368 | 369 | for p in self.plugins.itervalues(): 370 | dummy(p) 371 | 372 | def register_menu(self): 373 | self.orgmenu.create() 374 | 375 | def unregister_menu(self): 376 | vim.command(u'silent! aunmenu Org'.encode(u'utf-8')) 377 | 378 | def start(self): 379 | u""" Start orgmode and load all requested plugins 380 | """ 381 | plugins = orgmode.settings.get(u"org_plugins") 382 | 383 | if not plugins: 384 | echom(u'orgmode: No plugins registered.') 385 | 386 | if isinstance(plugins, basestring): 387 | try: 388 | self.register_plugin(plugins) 389 | except Exception, e: 390 | import traceback 391 | traceback.print_exc() 392 | elif isinstance(plugins, types.ListType) or \ 393 | isinstance(plugins, types.TupleType): 394 | for p in plugins: 395 | try: 396 | self.register_plugin(p) 397 | except Exception, e: 398 | echoe('Error in %s plugin:' % p) 399 | import traceback 400 | traceback.print_exc() 401 | 402 | return plugins 403 | 404 | 405 | ORGMODE = OrgMode() 406 | 407 | 408 | # vim: set noexpandtab: 409 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/Navigator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | from orgmode._vim import echo, ORGMODE, apply_count 6 | from orgmode.menu import Submenu, ActionEntry 7 | from orgmode.keybinding import Keybinding, MODE_VISUAL, MODE_OPERATOR, Plug 8 | from orgmode.liborgmode.documents import Direction 9 | 10 | 11 | class Navigator(object): 12 | u""" Implement navigation in org-mode documents """ 13 | 14 | def __init__(self): 15 | object.__init__(self) 16 | self.menu = ORGMODE.orgmenu + Submenu(u'&Navigate Headings') 17 | self.keybindings = [] 18 | 19 | @classmethod 20 | @apply_count 21 | def parent(cls, mode): 22 | u""" 23 | Focus parent heading 24 | 25 | :returns: parent heading or None 26 | """ 27 | heading = ORGMODE.get_document().current_heading() 28 | if not heading: 29 | if mode == u'visual': 30 | vim.command(u'normal! gv'.encode(u'utf-8')) 31 | else: 32 | echo(u'No heading found') 33 | return 34 | 35 | if not heading.parent: 36 | if mode == u'visual': 37 | vim.command(u'normal! gv'.encode(u'utf-8')) 38 | else: 39 | echo(u'No parent heading found') 40 | return 41 | 42 | p = heading.parent 43 | 44 | if mode == u'visual': 45 | cls._change_visual_selection(heading, p, direction=Direction.BACKWARD, parent=True) 46 | else: 47 | vim.current.window.cursor = (p.start_vim, p.level + 1) 48 | return p 49 | 50 | @classmethod 51 | @apply_count 52 | def parent_next_sibling(cls, mode): 53 | u""" 54 | Focus the parent's next sibling 55 | 56 | :returns: parent's next sibling heading or None 57 | """ 58 | heading = ORGMODE.get_document().current_heading() 59 | if not heading: 60 | if mode == u'visual': 61 | vim.command(u'normal! gv'.encode(u'utf-8')) 62 | else: 63 | echo(u'No heading found') 64 | return 65 | 66 | if not heading.parent or not heading.parent.next_sibling: 67 | if mode == u'visual': 68 | vim.command(u'normal! gv'.encode(u'utf-8')) 69 | else: 70 | echo(u'No parent heading found') 71 | return 72 | 73 | ns = heading.parent.next_sibling 74 | 75 | if mode == u'visual': 76 | cls._change_visual_selection(heading, ns, direction=Direction.FORWARD, parent=False) 77 | elif mode == u'operator': 78 | vim.current.window.cursor = (ns.start_vim, 0) 79 | else: 80 | vim.current.window.cursor = (ns.start_vim, ns.level + 1) 81 | return ns 82 | 83 | @classmethod 84 | def _change_visual_selection(cls, current_heading, heading, direction=Direction.FORWARD, noheadingfound=False, parent=False): 85 | current = vim.current.window.cursor[0] 86 | line_start, col_start = [int(i) for i in vim.eval(u'getpos("\'<")'.encode(u'utf-8'))[1:3]] 87 | line_end, col_end = [int(i) for i in vim.eval(u'getpos("\'>")'.encode(u'utf-8'))[1:3]] 88 | 89 | f_start = heading.start_vim 90 | f_end = heading.end_vim 91 | swap_cursor = True 92 | 93 | # << |visual start 94 | # selection end >> 95 | if current == line_start: 96 | if (direction == Direction.FORWARD and line_end < f_start) or noheadingfound and not direction == Direction.BACKWARD: 97 | swap_cursor = False 98 | 99 | # focus heading HERE 100 | # << |visual start 101 | # selection end >> 102 | 103 | # << |visual start 104 | # focus heading HERE 105 | # selection end >> 106 | if f_start < line_start and direction == Direction.BACKWARD: 107 | if current_heading.start_vim < line_start and not parent: 108 | line_start = current_heading.start_vim 109 | else: 110 | line_start = f_start 111 | 112 | elif (f_start < line_start or f_start < line_end) and not noheadingfound: 113 | line_start = f_start 114 | 115 | # << |visual start 116 | # selection end >> 117 | # focus heading HERE 118 | else: 119 | if direction == Direction.FORWARD: 120 | if line_end < f_start and not line_start == f_start - 1 and current_heading: 121 | # focus end of previous heading instead of beginning of next heading 122 | line_start = line_end 123 | line_end = f_start - 1 124 | else: 125 | # focus end of next heading 126 | line_start = line_end 127 | line_end = f_end 128 | elif direction == Direction.BACKWARD: 129 | if line_end < f_end: 130 | pass 131 | else: 132 | line_start = line_end 133 | line_end = f_end 134 | 135 | # << visual start 136 | # selection end| >> 137 | else: 138 | # focus heading HERE 139 | # << visual start 140 | # selection end| >> 141 | if line_start > f_start and line_end > f_end and not parent: 142 | line_end = f_end 143 | swap_cursor = False 144 | 145 | elif (line_start > f_start or line_start == f_start) and \ 146 | line_end <= f_end and direction == Direction.BACKWARD: 147 | line_end = line_start 148 | line_start = f_start 149 | 150 | # << visual start 151 | # selection end and focus heading end HERE| >> 152 | 153 | # << visual start 154 | # focus heading HERE 155 | # selection end| >> 156 | 157 | # << visual start 158 | # selection end| >> 159 | # focus heading HERE 160 | else: 161 | if direction == Direction.FORWARD: 162 | if line_end < f_start - 1: 163 | # focus end of previous heading instead of beginning of next heading 164 | line_end = f_start - 1 165 | else: 166 | # focus end of next heading 167 | line_end = f_end 168 | else: 169 | line_end = f_end 170 | swap_cursor = False 171 | 172 | move_col_start = u'%dl' % (col_start - 1) if (col_start - 1) > 0 and (col_start - 1) < 2000000000 else u'' 173 | move_col_end = u'%dl' % (col_end - 1) if (col_end - 1) > 0 and (col_end - 1) < 2000000000 else u'' 174 | swap = u'o' if swap_cursor else u'' 175 | 176 | vim.command(( 177 | u'normal! %dgg%s%s%dgg%s%s' % 178 | (line_start, move_col_start, vim.eval(u'visualmode()'.encode(u'utf-8')), line_end, move_col_end, swap)).encode(u'utf-8')) 179 | 180 | @classmethod 181 | def _focus_heading(cls, mode, direction=Direction.FORWARD, skip_children=False): 182 | u""" 183 | Focus next or previous heading in the given direction 184 | 185 | :direction: True for next heading, False for previous heading 186 | :returns: next heading or None 187 | """ 188 | d = ORGMODE.get_document() 189 | current_heading = d.current_heading() 190 | heading = current_heading 191 | focus_heading = None 192 | # FIXME this is just a piece of really ugly and unmaintainable code. It 193 | # should be rewritten 194 | if not heading: 195 | if direction == Direction.FORWARD and d.headings \ 196 | and vim.current.window.cursor[0] < d.headings[0].start_vim: 197 | # the cursor is in the meta information are, therefore focus 198 | # first heading 199 | focus_heading = d.headings[0] 200 | if not (heading or focus_heading): 201 | if mode == u'visual': 202 | # restore visual selection when no heading was found 203 | vim.command(u'normal! gv'.encode(u'utf-8')) 204 | else: 205 | echo(u'No heading found') 206 | return 207 | elif direction == Direction.BACKWARD: 208 | if vim.current.window.cursor[0] != heading.start_vim: 209 | # the cursor is in the body of the current heading, therefore 210 | # the current heading will be focused 211 | if mode == u'visual': 212 | line_start, col_start = [int(i) for i in vim.eval(u'getpos("\'<")'.encode(u'utf-8'))[1:3]] 213 | line_end, col_end = [int(i) for i in vim.eval(u'getpos("\'>")'.encode(u'utf-8'))[1:3]] 214 | if line_start >= heading.start_vim and line_end > heading.start_vim: 215 | focus_heading = heading 216 | else: 217 | focus_heading = heading 218 | 219 | # so far no heading has been found that the next focus should be on 220 | if not focus_heading: 221 | if not skip_children and direction == Direction.FORWARD and heading.children: 222 | focus_heading = heading.children[0] 223 | elif direction == Direction.FORWARD and heading.next_sibling: 224 | focus_heading = heading.next_sibling 225 | elif direction == Direction.BACKWARD and heading.previous_sibling: 226 | focus_heading = heading.previous_sibling 227 | if not skip_children: 228 | while focus_heading.children: 229 | focus_heading = focus_heading.children[-1] 230 | else: 231 | if direction == Direction.FORWARD: 232 | focus_heading = current_heading.next_heading 233 | else: 234 | focus_heading = current_heading.previous_heading 235 | 236 | noheadingfound = False 237 | if not focus_heading: 238 | if mode in (u'visual', u'operator'): 239 | # the cursor seems to be on the last or first heading of this 240 | # document and performes another next/previous operation 241 | focus_heading = heading 242 | noheadingfound = True 243 | else: 244 | if direction == Direction.FORWARD: 245 | echo(u'Already focussing last heading') 246 | else: 247 | echo(u'Already focussing first heading') 248 | return 249 | 250 | if mode == u'visual': 251 | cls._change_visual_selection(current_heading, focus_heading, direction=direction, noheadingfound=noheadingfound) 252 | elif mode == u'operator': 253 | if direction == Direction.FORWARD and vim.current.window.cursor[0] >= focus_heading.start_vim: 254 | vim.current.window.cursor = (focus_heading.end_vim, len(vim.current.buffer[focus_heading.end].decode(u'utf-8'))) 255 | else: 256 | vim.current.window.cursor = (focus_heading.start_vim, 0) 257 | else: 258 | vim.current.window.cursor = (focus_heading.start_vim, focus_heading.level + 1) 259 | if noheadingfound: 260 | return 261 | return focus_heading 262 | 263 | @classmethod 264 | @apply_count 265 | def previous(cls, mode, skip_children=False): 266 | u""" 267 | Focus previous heading 268 | """ 269 | return cls._focus_heading(mode, direction=Direction.BACKWARD, skip_children=skip_children) 270 | 271 | @classmethod 272 | @apply_count 273 | def next(cls, mode, skip_children=False): 274 | u""" 275 | Focus next heading 276 | """ 277 | return cls._focus_heading(mode, direction=Direction.FORWARD, skip_children=skip_children) 278 | 279 | def register(self): 280 | # normal mode 281 | self.keybindings.append(Keybinding(u'g{', Plug('OrgJumpToParentNormal', u':py ORGMODE.plugins[u"Navigator"].parent(mode=u"normal")'))) 282 | self.menu + ActionEntry(u'&Up', self.keybindings[-1]) 283 | self.keybindings.append(Keybinding(u'g}', Plug('OrgJumpToParentsSiblingNormal', u':py ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"normal")'))) 284 | self.menu + ActionEntry(u'&Down', self.keybindings[-1]) 285 | self.keybindings.append(Keybinding(u'{', Plug(u'OrgJumpToPreviousNormal', u':py ORGMODE.plugins[u"Navigator"].previous(mode=u"normal")'))) 286 | self.menu + ActionEntry(u'&Previous', self.keybindings[-1]) 287 | self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextNormal', u':py ORGMODE.plugins[u"Navigator"].next(mode=u"normal")'))) 288 | self.menu + ActionEntry(u'&Next', self.keybindings[-1]) 289 | 290 | # visual mode 291 | self.keybindings.append(Keybinding(u'g{', Plug(u'OrgJumpToParentVisual', u':py ORGMODE.plugins[u"Navigator"].parent(mode=u"visual")', mode=MODE_VISUAL))) 292 | self.keybindings.append(Keybinding(u'g}', Plug('OrgJumpToParentsSiblingVisual', u':py ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"visual")', mode=MODE_VISUAL))) 293 | self.keybindings.append(Keybinding(u'{', Plug(u'OrgJumpToPreviousVisual', u':py ORGMODE.plugins[u"Navigator"].previous(mode=u"visual")', mode=MODE_VISUAL))) 294 | self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextVisual', u':py ORGMODE.plugins[u"Navigator"].next(mode=u"visual")', mode=MODE_VISUAL))) 295 | 296 | # operator-pending mode 297 | self.keybindings.append(Keybinding(u'g{', Plug(u'OrgJumpToParentOperator', u':py ORGMODE.plugins[u"Navigator"].parent(mode=u"operator")', mode=MODE_OPERATOR))) 298 | self.keybindings.append(Keybinding(u'g}', Plug('OrgJumpToParentsSiblingOperator', u':py ORGMODE.plugins[u"Navigator"].parent_next_sibling(mode=u"operator")', mode=MODE_OPERATOR))) 299 | self.keybindings.append(Keybinding(u'{', Plug(u'OrgJumpToPreviousOperator', u':py ORGMODE.plugins[u"Navigator"].previous(mode=u"operator")', mode=MODE_OPERATOR))) 300 | self.keybindings.append(Keybinding(u'}', Plug(u'OrgJumpToNextOperator', u':py ORGMODE.plugins[u"Navigator"].next(mode=u"operator")', mode=MODE_OPERATOR))) 301 | 302 | # section wise movement (skip children) 303 | # normal mode 304 | self.keybindings.append(Keybinding(u'[[', Plug(u'OrgJumpToPreviousSkipChildrenNormal', u':py ORGMODE.plugins[u"Navigator"].previous(mode=u"normal", skip_children=True)'))) 305 | self.menu + ActionEntry(u'Ne&xt Same Level', self.keybindings[-1]) 306 | self.keybindings.append(Keybinding(u']]', Plug(u'OrgJumpToNextSkipChildrenNormal', u':py ORGMODE.plugins[u"Navigator"].next(mode=u"normal", skip_children=True)'))) 307 | self.menu + ActionEntry(u'Pre&vious Same Level', self.keybindings[-1]) 308 | 309 | # visual mode 310 | self.keybindings.append(Keybinding(u'[[', Plug(u'OrgJumpToPreviousSkipChildrenVisual', u':py ORGMODE.plugins[u"Navigator"].previous(mode=u"visual", skip_children=True)', mode=MODE_VISUAL))) 311 | self.keybindings.append(Keybinding(u']]', Plug(u'OrgJumpToNextSkipChildrenVisual', u':py ORGMODE.plugins[u"Navigator"].next(mode=u"visual", skip_children=True)', mode=MODE_VISUAL))) 312 | 313 | # operator-pending mode 314 | self.keybindings.append(Keybinding(u'[[', Plug(u'OrgJumpToPreviousSkipChildrenOperator', u':py ORGMODE.plugins[u"Navigator"].previous(mode=u"operator", skip_children=True)', mode=MODE_OPERATOR))) 315 | self.keybindings.append(Keybinding(u']]', Plug(u'OrgJumpToNextSkipChildrenOperator', u':py ORGMODE.plugins[u"Navigator"].next(mode=u"operator", skip_children=True)', mode=MODE_OPERATOR))) 316 | 317 | # vim: set noexpandtab: 318 | -------------------------------------------------------------------------------- /ftplugin/orgmode/plugins/Todo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | 5 | from orgmode._vim import echom, ORGMODE, apply_count, repeat, realign_tags 6 | from orgmode import settings 7 | from orgmode.liborgmode.base import Direction 8 | from orgmode.menu import Submenu, ActionEntry 9 | from orgmode.keybinding import Keybinding, Plug 10 | 11 | # temporary todo states for differnent orgmode buffers 12 | ORGTODOSTATES = {} 13 | 14 | 15 | def split_access_key(t): 16 | u""" 17 | :t: todo state 18 | 19 | :return: todo state and access key separated (TODO, ACCESS_KEY) 20 | """ 21 | if type(t) != unicode: 22 | return (None, None) 23 | 24 | idx = t.find(u'(') 25 | v, k = ((t[:idx], t[idx + 1:-1]) if t[idx + 1:-1] else (t, None)) if idx != -1 else (t, None) 26 | return (v, k) 27 | 28 | 29 | class Todo(object): 30 | u""" 31 | Todo plugin. 32 | 33 | Description taken from orgmode.org: 34 | 35 | You can use TODO keywords to indicate different sequential states in the 36 | process of working on an item, for example: 37 | 38 | ["TODO", "FEEDBACK", "VERIFY", "|", "DONE", "DELEGATED"] 39 | 40 | The vertical bar separates the TODO keywords (states that need action) from 41 | the DONE states (which need no further action). If you don't provide the 42 | separator bar, the last state is used as the DONE state. With this setup, 43 | the command ``,d`` will cycle an entry from TODO to FEEDBACK, then to 44 | VERIFY, and finally to DONE and DELEGATED. 45 | """ 46 | 47 | def __init__(self): 48 | u""" Initialize plugin """ 49 | object.__init__(self) 50 | # menu entries this plugin should create 51 | self.menu = ORGMODE.orgmenu + Submenu(u'&TODO Lists') 52 | 53 | # key bindings for this plugin 54 | # key bindings are also registered through the menu so only additional 55 | # bindings should be put in this variable 56 | self.keybindings = [] 57 | 58 | @classmethod 59 | def _get_next_state( 60 | cls, current_state, all_states, 61 | direction=Direction.FORWARD, interactive=False, next_set=False): 62 | u""" 63 | WTF is going on here!!! 64 | FIXME: reimplement this in a clean way :) 65 | 66 | :current_state: the current todo state 67 | :all_states: a list containing all todo states within sublists. 68 | The todo states may contain access keys 69 | :direction: direction of state or keyword set change (forward/backward) 70 | :interactive: if interactive and more than one todo sequence is 71 | specified, open a selection window 72 | :next_set: advance to the next keyword set in defined direction 73 | 74 | :return: return the next state as string, or NONE if the 75 | next state is no state. 76 | """ 77 | if not all_states: 78 | return 79 | 80 | def find_current_todo_state(c, a, stop=0): 81 | u""" 82 | :c: current todo state 83 | :a: list of todo states 84 | :stop: internal parameter for parsing only two levels of lists 85 | 86 | :return: first position of todo state in list in the form 87 | (IDX_TOPLEVEL, IDX_SECOND_LEVEL (0|1), IDX_OF_ITEM) 88 | """ 89 | for i in xrange(0, len(a)): 90 | if type(a[i]) in (tuple, list) and stop < 2: 91 | r = find_current_todo_state(c, a[i], stop=stop + 1) 92 | if r: 93 | r.insert(0, i) 94 | return r 95 | # ensure that only on the second level of sublists todo states 96 | # are found 97 | if type(a[i]) == unicode and stop == 2: 98 | _i = split_access_key(a[i])[0] 99 | if c == _i: 100 | return [i] 101 | 102 | ci = find_current_todo_state(current_state, all_states) 103 | 104 | if not ci: 105 | if next_set and direction == Direction.BACKWARD: 106 | echom(u'Already at the first keyword set') 107 | return current_state 108 | 109 | return split_access_key(all_states[0][0][0] if all_states[0][0] else all_states[0][1][0])[0] \ 110 | if direction == Direction.FORWARD else \ 111 | split_access_key(all_states[0][1][-1] if all_states[0][1] else all_states[0][0][-1])[0] 112 | elif next_set: 113 | if direction == Direction.FORWARD and ci[0] + 1 < len(all_states[ci[0]]): 114 | echom(u'Keyword set: %s | %s' % (u', '.join(all_states[ci[0] + 1][0]), u', '.join(all_states[ci[0] + 1][1]))) 115 | return split_access_key( 116 | all_states[ci[0] + 1][0][0] if all_states[ci[0] + 1][0] else all_states[ci[0] + 1][1][0])[0] 117 | elif current_state is not None and direction == Direction.BACKWARD and ci[0] - 1 >= 0: 118 | echom(u'Keyword set: %s | %s' % (u', '.join(all_states[ci[0] - 1][0]), u', '.join(all_states[ci[0] - 1][1]))) 119 | return split_access_key( 120 | all_states[ci[0] - 1][0][0] if all_states[ci[0] - 1][0] else all_states[ci[0] - 1][1][0])[0] 121 | else: 122 | echom(u'Already at the %s keyword set' % (u'first' if direction == Direction.BACKWARD else u'last')) 123 | return current_state 124 | else: 125 | next_pos = ci[2] + 1 if direction == Direction.FORWARD else ci[2] - 1 126 | if direction == Direction.FORWARD: 127 | if next_pos < len(all_states[ci[0]][ci[1]]): 128 | # select next state within done or todo states 129 | return split_access_key(all_states[ci[0]][ci[1]][next_pos])[0] 130 | 131 | elif not ci[1] and next_pos - len(all_states[ci[0]][ci[1]]) < len(all_states[ci[0]][ci[1] + 1]): 132 | # finished todo states, jump to done states 133 | return split_access_key(all_states[ci[0]][ci[1] + 1][next_pos - len(all_states[ci[0]][ci[1]])])[0] 134 | else: 135 | if next_pos >= 0: 136 | # select previous state within done or todo states 137 | return split_access_key(all_states[ci[0]][ci[1]][next_pos])[0] 138 | 139 | elif ci[1] and len(all_states[ci[0]][ci[1] - 1]) + next_pos < len(all_states[ci[0]][ci[1] - 1]): 140 | # finished done states, jump to todo states 141 | return split_access_key(all_states[ci[0]][ci[1] - 1][len(all_states[ci[0]][ci[1] - 1]) + next_pos])[0] 142 | 143 | @classmethod 144 | @realign_tags 145 | @repeat 146 | @apply_count 147 | def toggle_todo_state(cls, direction=Direction.FORWARD, interactive=False, next_set=False): 148 | u""" Toggle state of TODO item 149 | 150 | :returns: The changed heading 151 | """ 152 | d = ORGMODE.get_document(allow_dirty=True) 153 | 154 | # get heading 155 | heading = d.find_current_heading() 156 | if not heading: 157 | vim.eval(u'feedkeys("^", "n")') 158 | return 159 | 160 | todo_states = d.get_todo_states(strip_access_key=False) 161 | # get todo states 162 | if not todo_states: 163 | echom(u'No todo keywords configured.') 164 | return 165 | 166 | current_state = heading.todo 167 | 168 | # get new state interactively 169 | if interactive: 170 | # determine position of the interactive prompt 171 | prompt_pos = settings.get(u'org_todo_prompt_position', u'botright') 172 | if prompt_pos not in [u'botright', u'topleft']: 173 | prompt_pos = u'botright' 174 | 175 | # pass todo states to new window 176 | ORGTODOSTATES[d.bufnr] = todo_states 177 | settings.set( 178 | u'org_current_state_%d' % d.bufnr, 179 | current_state if current_state is not None else u'', overwrite=True) 180 | todo_buffer_exists = bool(int(vim.eval(( 181 | u'bufexists("org:todo/%d")' % (d.bufnr, )).encode(u'utf-8')))) 182 | if todo_buffer_exists: 183 | # if the buffer already exists, reuse it 184 | vim.command(( 185 | u'%s sbuffer org:todo/%d' % (prompt_pos, d.bufnr, )).encode(u'utf-8')) 186 | else: 187 | # create a new window 188 | vim.command(( 189 | u'keepalt %s %dsplit org:todo/%d' % (prompt_pos, len(todo_states), d.bufnr)).encode(u'utf-8')) 190 | else: 191 | new_state = Todo._get_next_state( 192 | current_state, todo_states, direction=direction, 193 | interactive=interactive, next_set=next_set) 194 | cls.set_todo_state(new_state) 195 | 196 | # plug 197 | plug = u'OrgTodoForward' 198 | if direction == Direction.BACKWARD: 199 | plug = u'OrgTodoBackward' 200 | 201 | return plug 202 | 203 | @classmethod 204 | def set_todo_state(cls, state): 205 | u""" Set todo state for buffer. 206 | 207 | :bufnr: Number of buffer the todo state should be updated for 208 | :state: The new todo state 209 | """ 210 | lineno, colno = vim.current.window.cursor 211 | d = ORGMODE.get_document(allow_dirty=True) 212 | heading = d.find_current_heading() 213 | 214 | if not heading: 215 | return 216 | 217 | current_state = heading.todo 218 | 219 | # set new headline 220 | heading.todo = state 221 | d.write_heading(heading) 222 | 223 | # move cursor along with the inserted state only when current position 224 | # is in the heading; otherwite do nothing 225 | if heading.start_vim == lineno and colno > heading.level: 226 | if current_state is not None and \ 227 | colno <= heading.level + len(current_state): 228 | # the cursor is actually on the todo keyword 229 | # move it back to the beginning of the keyword in that case 230 | vim.current.window.cursor = (lineno, heading.level + 1) 231 | else: 232 | # the cursor is somewhere in the text, move it along 233 | if current_state is None and state is None: 234 | offset = 0 235 | elif current_state is None and state is not None: 236 | offset = len(state) + 1 237 | elif current_state is not None and state is None: 238 | offset = -len(current_state) - 1 239 | else: 240 | offset = len(state) - len(current_state) 241 | vim.current.window.cursor = (lineno, colno + offset) 242 | 243 | @classmethod 244 | def init_org_todo(cls): 245 | u""" Initialize org todo selection window. 246 | """ 247 | bufnr = int(vim.current.buffer.name.split('/')[-1]) 248 | all_states = ORGTODOSTATES.get(bufnr, None) 249 | 250 | # because timeoutlen can only be set globally it needs to be stored and restored later 251 | vim.command(u'let g:org_sav_timeoutlen=&timeoutlen'.encode(u'utf-8')) 252 | vim.command(u'au orgmode BufEnter :if ! exists("g:org_sav_timeoutlen")|let g:org_sav_timeoutlen=&timeoutlen|set timeoutlen=1|endif'.encode(u'utf-8')) 253 | vim.command(u'au orgmode BufLeave :if exists("g:org_sav_timeoutlen")|let &timeoutlen=g:org_sav_timeoutlen|unlet g:org_sav_timeoutlen|endif'.encode(u'utf-8')) 254 | # make window a scratch window and set the statusline differently 255 | vim.command(u'setlocal nolist tabstop=16 buftype=nofile timeout timeoutlen=1 winfixheight'.encode(u'utf-8')) 256 | vim.command((u'setlocal statusline=Org\\ todo\\ (%s)' % vim.eval((u'fnameescape(fnamemodify(bufname(%d), ":t"))' % bufnr).encode(u'utf-8'))).encode(u'utf-8')) 257 | vim.command((u'nnoremap :%sbw' % (vim.eval(u'bufnr("%")'.encode(u'utf-8')), )).encode(u'utf-8')) 258 | vim.command(u'nnoremap :let g:org_state = fnameescape(expand(""))bwexec "py ORGMODE.plugins[u\'Todo\'].set_todo_state(\'".g:org_state."\')"unlet! g:org_state'.encode(u'utf-8')) 259 | 260 | if all_states is None: 261 | vim.command(u'bw'.encode(u'utf-8')) 262 | echom(u'No todo states avaiable for buffer %s' % vim.current.buffer.name) 263 | 264 | for l in xrange(0, len(all_states)): 265 | res = u'' 266 | for j in xrange(0, 2): 267 | if j < len(all_states[l]): 268 | for i in all_states[l][j]: 269 | if type(i) != unicode: 270 | continue 271 | v, k = split_access_key(i) 272 | if k: 273 | res += (u'\t' if res else u'') + u'[%s] %s' % (k, v) 274 | # map access keys to callback that updates current heading 275 | # map selection keys 276 | vim.command((u'nnoremap %s :bwpy ORGMODE.plugins[u"Todo"].set_todo_state("%s".decode(u"utf-8"))' % (k, v)).encode(u'utf-8')) 277 | elif v: 278 | res += (u'\t' if res else u'') + v 279 | if res: 280 | if l == 0: 281 | # WORKAROUND: the cursor can not be positioned properly on 282 | # the first line. Another line is just inserted and it 283 | # works great 284 | vim.current.buffer[0] = u''.encode(u'utf-8') 285 | vim.current.buffer.append(res.encode(u'utf-8')) 286 | 287 | # position the cursor of the current todo item 288 | vim.command(u'normal! G'.encode(u'utf-8')) 289 | current_state = settings.unset(u'org_current_state_%d' % bufnr) 290 | found = False 291 | if current_state is not None and current_state != '': 292 | for i in xrange(0, len(vim.current.buffer)): 293 | idx = vim.current.buffer[i].find(current_state) 294 | if idx != -1: 295 | vim.current.window.cursor = (i + 1, idx) 296 | found = True 297 | break 298 | if not found: 299 | vim.current.window.cursor = (2, 4) 300 | 301 | # finally make buffer non modifiable 302 | vim.command(u'setfiletype orgtodo'.encode(u'utf-8')) 303 | vim.command(u'setlocal nomodifiable'.encode(u'utf-8')) 304 | 305 | # remove temporary todo states for the current buffer 306 | del ORGTODOSTATES[bufnr] 307 | 308 | def register(self): 309 | u""" 310 | Registration of plugin. Key bindings and other initialization should be done. 311 | """ 312 | self.keybindings.append(Keybinding(u'ct', Plug( 313 | u'OrgTodoToggleNonInteractive', 314 | u':py ORGMODE.plugins[u"Todo"].toggle_todo_state(interactive=False)'))) 315 | self.menu + ActionEntry(u'&TODO/DONE/-', self.keybindings[-1]) 316 | 317 | self.keybindings.append(Keybinding(u'd', Plug( 318 | u'OrgTodoToggleInteractive', 319 | u':py ORGMODE.plugins[u"Todo"].toggle_todo_state(interactive=True)'))) 320 | self.menu + ActionEntry(u'&TODO/DONE/- (interactiv)', self.keybindings[-1]) 321 | 322 | # add submenu 323 | submenu = self.menu + Submenu(u'Select &keyword') 324 | 325 | self.keybindings.append(Keybinding(u'', Plug( 326 | u'OrgTodoForward', 327 | u':py ORGMODE.plugins[u"Todo"].toggle_todo_state()'))) 328 | submenu + ActionEntry(u'&Next keyword', self.keybindings[-1]) 329 | 330 | self.keybindings.append(Keybinding(u'', Plug( 331 | u'OrgTodoBackward', 332 | u':py ORGMODE.plugins[u"Todo"].toggle_todo_state(direction=2)'))) 333 | submenu + ActionEntry(u'&Previous keyword', self.keybindings[-1]) 334 | 335 | self.keybindings.append(Keybinding(u'', Plug( 336 | u'OrgTodoSetForward', 337 | u':py ORGMODE.plugins[u"Todo"].toggle_todo_state(next_set=True)'))) 338 | submenu + ActionEntry(u'Next keyword &set', self.keybindings[-1]) 339 | 340 | self.keybindings.append(Keybinding(u'', Plug( 341 | u'OrgTodoSetBackward', 342 | u':py ORGMODE.plugins[u"Todo"].toggle_todo_state(direction=2, next_set=True)'))) 343 | submenu + ActionEntry(u'Previous &keyword set', self.keybindings[-1]) 344 | 345 | settings.set(u'org_todo_keywords', [u'TODO'.encode(u'utf-8'), u'|'.encode(u'utf-8'), u'DONE'.encode(u'utf-8')]) 346 | 347 | settings.set(u'org_todo_prompt_position', u'botright') 348 | 349 | vim.command(u'au orgmode BufReadCmd org:todo/* :py ORGMODE.plugins[u"Todo"].init_org_todo()'.encode(u'utf-8')) 350 | 351 | # vim: set noexpandtab: 352 | -------------------------------------------------------------------------------- /ftplugin/orgmode/liborgmode/dom_obj.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | dom object 5 | ~~~~~~~~~~ 6 | 7 | TODO: explain this :) 8 | """ 9 | 10 | import re 11 | from UserList import UserList 12 | from orgmode.liborgmode.base import MultiPurposeList, flatten_list 13 | 14 | # breaking down tasks regex 15 | REGEX_SUBTASK = re.compile(r'\[(\d*)/(\d*)\]') 16 | REGEX_SUBTASK_PERCENT = re.compile(r'\[(\d*)%\]') 17 | 18 | # heading regex 19 | REGEX_HEADING = re.compile( 20 | r'^(?P\*+)(\s+(?P.*?))?\s*(\s(?P<tags>:[\w_:@]+:))?$', 21 | flags=re.U | re.L) 22 | REGEX_TAG = re.compile( 23 | r'^\s*((?P<title>[^\s]*?)\s+)?(?P<tags>:[\w_:@]+:)$', 24 | flags=re.U | re.L) 25 | REGEX_TODO = re.compile(r'^[^\s]*$') 26 | 27 | # checkbox regex: 28 | # - [ ] checkbox item 29 | # - [X] checkbox item 30 | # - [ ] 31 | # - no status checkbox 32 | UnOrderListType = [u'-', u'+', u'*'] 33 | OrderListType = [u'.', u')'] 34 | REGEX_CHECKBOX = re.compile( 35 | r'^(?P<level>\s*)(?P<type>[%s]|([a-zA-Z]|[\d]+)[%s])(\s+(?P<status>\[.\]))?\s*(?P<title>.*)$' 36 | % (''.join(UnOrderListType), ''.join(OrderListType)), flags=re.U | re.L) 37 | 38 | 39 | class DomObj(object): 40 | u""" 41 | A DomObj is DOM structure element, like Heading and Checkbox. 42 | Its purpose is to abstract the same parts of Heading and Checkbox objects, 43 | and make code reusable. 44 | 45 | All methods and properties are extracted from Heading object. 46 | Heading and Checkbox objects inherit from DomObj, and override some specific 47 | methods in their own objects. 48 | 49 | Normally, we don't intend to use DomObj directly. However, we can add some more 50 | DOM structure element based on this class to make code more concise. 51 | """ 52 | 53 | def __init__(self, level=1, title=u'', body=None): 54 | u""" 55 | :level: Level of the dom object 56 | :title: Title of the dom object 57 | :body: Body of the dom object 58 | """ 59 | object.__init__(self) 60 | 61 | self._document = None 62 | self._parent = None 63 | self._previous_sibling = None 64 | self._next_sibling = None 65 | self._children = MultiPurposeList() 66 | self._orig_start = None 67 | self._orig_len = 0 68 | 69 | self._level = level 70 | # title 71 | self._title = u'' 72 | if title: 73 | self.title = title 74 | 75 | # body 76 | self._dirty_body = False 77 | self._body = MultiPurposeList(on_change=self.set_dirty_body) 78 | if body: 79 | self.body = body 80 | 81 | def __unicode__(self): 82 | return u'<dom obj level=%s, title=%s>' % (level, title) 83 | 84 | def __str__(self): 85 | return self.__unicode__().encode(u'utf-8') 86 | 87 | def __len__(self): 88 | # 1 is for the heading's title 89 | return 1 + len(self.body) 90 | 91 | @property 92 | def is_dirty(self): 93 | u""" Return True if the dom obj body is marked dirty """ 94 | return self._dirty_body 95 | 96 | @property 97 | def is_dirty_body(self): 98 | u""" Return True if the dom obj body is marked dirty """ 99 | return self._dirty_body 100 | 101 | def get_index_in_parent_list(self): 102 | """ Retrieve the index value of current dom obj in the parents list of 103 | dom objs. This works also for top level dom objs. 104 | 105 | :returns: Index value or None if dom obj doesn't have a 106 | parent/document or is not in the list of dom objs 107 | """ 108 | l = self.get_parent_list() 109 | if l: 110 | return l.index(self) 111 | 112 | def get_parent_list(self): 113 | """ Retrieve the parents list of dom objs. This works also for top 114 | level dom objs. 115 | 116 | :returns: List of dom objs or None if dom objs doesn't have a 117 | parent/document or is not in the list of dom objs 118 | """ 119 | if self.parent: 120 | if self in self.parent.children: 121 | return self.parent.children 122 | 123 | def set_dirty(self): 124 | u""" Mark the dom objs and body dirty so that it will be rewritten when 125 | saving the document """ 126 | if self._document: 127 | self._document.set_dirty_document() 128 | 129 | def set_dirty_body(self): 130 | u""" Mark the dom objs' body dirty so that it will be rewritten when 131 | saving the document """ 132 | self._dirty_body = True 133 | if self._document: 134 | self._document.set_dirty_document() 135 | 136 | @property 137 | def document(self): 138 | u""" Read only access to the document. If you want to change the 139 | document, just assign the dom obj to another document """ 140 | return self._document 141 | 142 | @property 143 | def parent(self): 144 | u""" Access to the parent dom obj """ 145 | return self._parent 146 | 147 | @property 148 | def number_of_parents(self): 149 | u""" Access to the number of parent dom objs before reaching the root 150 | document """ 151 | def count_parents(h): 152 | if h.parent: 153 | return 1 + count_parents(h.parent) 154 | else: 155 | return 0 156 | return count_parents(self) 157 | 158 | @property 159 | def previous_sibling(self): 160 | u""" Access to the previous dom obj that's a sibling of the current one 161 | """ 162 | return self._previous_sibling 163 | 164 | @property 165 | def next_sibling(self): 166 | u""" Access to the next dom obj that's a sibling of the current one """ 167 | return self._next_sibling 168 | 169 | @property 170 | def previous_item(self): 171 | u""" Serialized access to the previous dom obj """ 172 | if self.previous_sibling: 173 | h = self.previous_sibling 174 | while h.children: 175 | h = h.children[-1] 176 | return h 177 | elif self.parent: 178 | return self.parent 179 | 180 | @property 181 | def next_item(self): 182 | u""" Serialized access to the next dom obj """ 183 | if self.children: 184 | return self.children[0] 185 | elif self.next_sibling: 186 | return self.next_sibling 187 | else: 188 | h = self.parent 189 | while h: 190 | if h.next_sibling: 191 | return h.next_sibling 192 | else: 193 | h = h.parent 194 | 195 | @property 196 | def start(self): 197 | u""" Access to the starting line of the dom obj """ 198 | if self.document is None: 199 | return self._orig_start 200 | 201 | # static computation of start 202 | if not self.document.is_dirty: 203 | return self._orig_start 204 | 205 | # dynamic computation of start, really slow! 206 | def compute_start(h): 207 | if h: 208 | return len(h) + compute_start(h.previous_item) 209 | return compute_start(self.previous_item) 210 | 211 | @property 212 | def start_vim(self): 213 | if self.start is not None: 214 | return self.start + 1 215 | 216 | @property 217 | def end(self): 218 | u""" Access to the ending line of the dom obj """ 219 | if self.start is not None: 220 | return self.start + len(self.body) 221 | 222 | @property 223 | def end_vim(self): 224 | if self.end is not None: 225 | return self.end + 1 226 | 227 | @property 228 | def end_of_last_child(self): 229 | u""" Access to end of the last child """ 230 | if self.children: 231 | child = self.children[-1] 232 | while child.children: 233 | child = child.children[-1] 234 | return child.end 235 | return self.end 236 | 237 | @property 238 | def end_of_last_child_vim(self): 239 | return self.end_of_last_child + 1 240 | 241 | def children(): 242 | u""" Subheadings of the current dom obj """ 243 | def fget(self): 244 | return self._children 245 | 246 | def fset(self, value): 247 | v = value 248 | if type(v) in (list, tuple) or isinstance(v, UserList): 249 | v = flatten_list(v) 250 | self._children[:] = v 251 | 252 | def fdel(self): 253 | del self.children[:] 254 | 255 | return locals() 256 | children = property(**children()) 257 | 258 | @property 259 | def first_child(self): 260 | u""" Access to the first child dom obj or None if no children exist """ 261 | if self.children: 262 | return self.children[0] 263 | 264 | @property 265 | def last_child(self): 266 | u""" Access to the last child dom obj or None if no children exist """ 267 | if self.children: 268 | return self.children[-1] 269 | 270 | def level(): 271 | u""" Access to the dom obj level """ 272 | def fget(self): 273 | return self._level 274 | 275 | def fset(self, value): 276 | self._level = int(value) 277 | self.set_dirty() 278 | 279 | def fdel(self): 280 | self.level = None 281 | 282 | return locals() 283 | level = property(**level()) 284 | 285 | def title(): 286 | u""" Title of current dom object """ 287 | def fget(self): 288 | return self._title.strip() 289 | 290 | def fset(self, value): 291 | if type(value) not in (unicode, str): 292 | raise ValueError(u'Title must be a string.') 293 | v = value 294 | if type(v) == str: 295 | v = v.decode(u'utf-8') 296 | self._title = v.strip() 297 | self.set_dirty() 298 | 299 | def fdel(self): 300 | self.title = u'' 301 | 302 | return locals() 303 | title = property(**title()) 304 | 305 | def body(): 306 | u""" Holds the content belonging to the heading """ 307 | def fget(self): 308 | return self._body 309 | 310 | def fset(self, value): 311 | if type(value) in (list, tuple) or isinstance(value, UserList): 312 | self._body[:] = flatten_list(value) 313 | elif type(value) in (str, ): 314 | self._body[:] = value.decode('utf-8').split(u'\n') 315 | elif type(value) in (unicode, ): 316 | self._body[:] = value.split(u'\n') 317 | else: 318 | self.body = list(unicode(value)) 319 | 320 | def fdel(self): 321 | self.body = [] 322 | 323 | return locals() 324 | body = property(**body()) 325 | 326 | 327 | class DomObjList(MultiPurposeList): 328 | u""" 329 | A Dom Obj List 330 | """ 331 | def __init__(self, initlist=None, obj=None): 332 | """ 333 | :initlist: Initial data 334 | :obj: Link to a concrete Heading or Document object 335 | """ 336 | # it's not necessary to register a on_change hook because the heading 337 | # list will itself take care of marking headings dirty or adding 338 | # headings to the deleted headings list 339 | MultiPurposeList.__init__(self) 340 | 341 | self._obj = obj 342 | 343 | # initialization must be done here, because 344 | # self._document is not initialized when the 345 | # constructor of MultiPurposeList is called 346 | if initlist: 347 | self.extend(initlist) 348 | 349 | @classmethod 350 | def is_domobj(cls, obj): 351 | return isinstance(obj, DomObj) 352 | 353 | def _get_document(self): 354 | if self.__class__.is_domobj(self._obj): 355 | return self._obj._document 356 | return self._obj 357 | 358 | def __setitem__(self, i, item): 359 | if not self.__class__.is_domobj(item): 360 | raise ValueError(u'Item is not a Dom obj!') 361 | if item in self: 362 | raise ValueError(u'Dom obj is already part of this list!') 363 | # self._add_to_deleted_domobjs(self[i]) 364 | 365 | # self._associate_domobj(item, \ 366 | # self[i - 1] if i - 1 >= 0 else None, \ 367 | # self[i + 1] if i + 1 < len(self) else None) 368 | MultiPurposeList.__setitem__(self, i, item) 369 | 370 | def __setslice__(self, i, j, other): 371 | o = other 372 | if self.__class__.is_domobj(o): 373 | o = (o, ) 374 | o = flatten_list(o) 375 | for item in o: 376 | if not self.__class__.is_domobj(item): 377 | raise ValueError(u'List contains items that are not a Dom obj!') 378 | i = max(i, 0) 379 | j = max(j, 0) 380 | # self._add_to_deleted_domobjs(self[i:j]) 381 | # self._associate_domobj(o, \ 382 | # self[i - 1] if i - 1 >= 0 and i < len(self) else None, \ 383 | # self[j] if j >= 0 and j < len(self) else None) 384 | MultiPurposeList.__setslice__(self, i, j, o) 385 | 386 | def __delitem__(self, i, taint=True): 387 | item = self[i] 388 | if item.previous_sibling: 389 | item.previous_sibling._next_sibling = item.next_sibling 390 | if item.next_sibling: 391 | item.next_sibling._previous_sibling = item.previous_sibling 392 | 393 | # if taint: 394 | # self._add_to_deleted_domobjs(item) 395 | MultiPurposeList.__delitem__(self, i) 396 | 397 | def __delslice__(self, i, j, taint=True): 398 | i = max(i, 0) 399 | j = max(j, 0) 400 | items = self[i:j] 401 | if items: 402 | first = items[0] 403 | last = items[-1] 404 | if first.previous_sibling: 405 | first.previous_sibling._next_sibling = last.next_sibling 406 | if last.next_sibling: 407 | last.next_sibling._previous_sibling = first.previous_sibling 408 | # if taint: 409 | # self._add_to_deleted_domobjs(items) 410 | MultiPurposeList.__delslice__(self, i, j) 411 | 412 | def __iadd__(self, other): 413 | o = other 414 | if self.__class__.is_domobj(o): 415 | o = (o, ) 416 | for item in flatten_list(o): 417 | if not self.__class__.is_domobj(item): 418 | raise ValueError(u'List contains items that are not a Dom obj!') 419 | # self._associate_domobj(o, self[-1] if len(self) > 0 else None, None) 420 | return MultiPurposeList.__iadd__(self, o) 421 | 422 | def __imul__(self, n): 423 | # TODO das müsste eigentlich ein klonen von objekten zur Folge haben 424 | return MultiPurposeList.__imul__(self, n) 425 | 426 | def append(self, item, taint=True): 427 | if not self.__class__.is_domobj(item): 428 | raise ValueError(u'Item is not a heading!') 429 | if item in self: 430 | raise ValueError(u'Heading is already part of this list!') 431 | # self._associate_domobj( 432 | # item, self[-1] if len(self) > 0 else None, 433 | # None, taint=taint) 434 | MultiPurposeList.append(self, item) 435 | 436 | def insert(self, i, item, taint=True): 437 | # self._associate_domobj( 438 | # item, 439 | # self[i - 1] if i - 1 >= 0 and i - 1 < len(self) else None, 440 | # self[i] if i >= 0 and i < len(self) else None, taint=taint) 441 | MultiPurposeList.insert(self, i, item) 442 | 443 | def pop(self, i=-1): 444 | item = self[i] 445 | # self._add_to_deleted_domobjs(item) 446 | del self[i] 447 | return item 448 | 449 | def remove_slice(self, i, j, taint=True): 450 | self.__delslice__(i, j, taint=taint) 451 | 452 | def remove(self, item, taint=True): 453 | self.__delitem__(self.index(item), taint=taint) 454 | 455 | def reverse(self): 456 | MultiPurposeList.reverse(self) 457 | prev_h = None 458 | for h in self: 459 | h._previous_sibling = prev_h 460 | h._next_sibling = None 461 | prev_h._next_sibling = h 462 | h.set_dirty() 463 | prev_h = h 464 | 465 | def sort(self, *args, **kwds): 466 | MultiPurposeList.sort(*args, **kwds) 467 | prev_h = None 468 | for h in self: 469 | h._previous_sibling = prev_h 470 | h._next_sibling = None 471 | prev_h._next_sibling = h 472 | h.set_dirty() 473 | prev_h = h 474 | 475 | def extend(self, other): 476 | o = other 477 | if self.__class__.is_domobj(o): 478 | o = (o, ) 479 | for item in o: 480 | if not self.__class__.is_domobj(item): 481 | raise ValueError(u'List contains items that are not a heading!') 482 | # self._associate_domobj(o, self[-1] if len(self) > 0 else None, None) 483 | MultiPurposeList.extend(self, o) 484 | 485 | 486 | # vim: set noexpandtab: 487 | -------------------------------------------------------------------------------- /syntax/org.vim: -------------------------------------------------------------------------------- 1 | " Support org authoring markup as closely as possible 2 | " (we're adding two markdown-like variants for =code= and blockquotes) 3 | " ----------------------------------------------------------------------------- 4 | 5 | " Inline markup 6 | " *bold*, /italic/, _underline_, +strike-through+, =code=, ~verbatim~ 7 | " Note: 8 | " - /italic/ is rendered as reverse in most terms (works fine in gVim, though) 9 | " - +strike-through+ doesn't work on Vim / gVim 10 | " - the non-standard `code' markup is also supported 11 | " - =code= and ~verbatim~ are also supported as block-level markup, see below. 12 | " Ref: http://orgmode.org/manual/Emphasis-and-monospace.html 13 | "syntax match org_bold /\*[^ ]*\*/ 14 | 15 | " FIXME: Always make org_bold syntax define before org_heading syntax 16 | " to make sure that org_heading syntax got higher priority(help :syn-priority) than org_bold. 17 | " If there is any other good solution, please help fix it. 18 | syntax region org_bold start="\S\@<=\*\|\*\S\@=" end="\S\@<=\*\|\*\S\@=" keepend oneline 19 | syntax region org_italic start="\S\@<=\/\|\/\S\@=" end="\S\@<=\/\|\/\S\@=" keepend oneline 20 | syntax region org_underline start="\S\@<=_\|_\S\@=" end="\S\@<=_\|_\S\@=" keepend oneline 21 | syntax region org_code start="\S\@<==\|=\S\@=" end="\S\@<==\|=\S\@=" keepend oneline 22 | syntax region org_code start="\S\@<=`\|`\S\@=" end="\S\@<='\|'\S\@=" keepend oneline 23 | syntax region org_verbatim start="\S\@<=\~\|\~\S\@=" end="\S\@<=\~\|\~\S\@=" keepend oneline 24 | 25 | hi def org_bold term=bold cterm=bold gui=bold 26 | hi def org_italic term=italic cterm=italic gui=italic 27 | hi def org_underline term=underline cterm=underline gui=underline 28 | 29 | " Headings: {{{1 30 | " Load Settings: {{{2 31 | if !exists('g:org_heading_highlight_colors') 32 | let g:org_heading_highlight_colors = ['Title', 'Constant', 'Identifier', 'Statement', 'PreProc', 'Type', 'Special'] 33 | endif 34 | 35 | if !exists('g:org_heading_highlight_levels') 36 | let g:org_heading_highlight_levels = len(g:org_heading_highlight_colors) 37 | endif 38 | 39 | if !exists('g:org_heading_shade_leading_stars') 40 | let g:org_heading_shade_leading_stars = 1 41 | endif 42 | 43 | " Enable Syntax HL: {{{2 44 | unlet! s:i s:j s:contains 45 | let s:i = 1 46 | let s:j = len(g:org_heading_highlight_colors) 47 | let s:contains = ' contains=org_timestamp,org_timestamp_inactive,org_subtask_percent,org_subtask_number,org_subtask_percent_100,org_subtask_number_all,org_list_checkbox,org_bold,org_italic,org_underline,org_code,org_verbatim' 48 | if g:org_heading_shade_leading_stars == 1 49 | let s:contains = s:contains . ',org_shade_stars' 50 | syntax match org_shade_stars /^\*\{2,\}/me=e-1 contained 51 | hi def link org_shade_stars Ignore 52 | else 53 | hi clear org_shade_stars 54 | endif 55 | 56 | while s:i <= g:org_heading_highlight_levels 57 | exec 'syntax match org_heading' . s:i . ' /^\*\{' . s:i . '\}\s.*/' . s:contains 58 | exec 'hi def link org_heading' . s:i . ' ' . g:org_heading_highlight_colors[(s:i - 1) % s:j] 59 | let s:i += 1 60 | endwhile 61 | unlet! s:i s:j s:contains 62 | 63 | " Todo Keywords: {{{1 64 | " Load Settings: {{{2 65 | if !exists('g:org_todo_keywords') 66 | let g:org_todo_keywords = ['TODO', '|', 'DONE'] 67 | endif 68 | 69 | if !exists('g:org_todo_keyword_faces') 70 | let g:org_todo_keyword_faces = [] 71 | endif 72 | 73 | " Enable Syntax HL: {{{2 74 | let s:todo_headings = '' 75 | let s:i = 1 76 | while s:i <= g:org_heading_highlight_levels 77 | if s:todo_headings == '' 78 | let s:todo_headings = 'containedin=org_heading' . s:i 79 | else 80 | let s:todo_headings = s:todo_headings . ',org_heading' . s:i 81 | endif 82 | let s:i += 1 83 | endwhile 84 | unlet! s:i 85 | 86 | if !exists('g:loaded_org_syntax') 87 | let g:loaded_org_syntax = 1 88 | 89 | function! OrgExtendHighlightingGroup(base_group, new_group, settings) 90 | let l:base_hi = '' 91 | redir => l:base_hi 92 | silent execute 'highlight ' . a:base_group 93 | redir END 94 | let l:group_hi = substitute(split(l:base_hi, '\n')[0], '^' . a:base_group . '\s\+xxx', '', '') 95 | execute 'highlight ' . a:new_group . l:group_hi . ' ' . a:settings 96 | endfunction 97 | 98 | function! OrgInterpretFaces(faces) 99 | let l:res_faces = '' 100 | if type(a:faces) == 3 101 | let l:style = [] 102 | for l:f in a:faces 103 | let l:_f = [l:f] 104 | if type(l:f) == 3 105 | let l:_f = l:f 106 | endif 107 | for l:g in l:_f 108 | if type(l:g) == 1 && l:g =~ '^:' 109 | if l:g !~ '[\t ]' 110 | continue 111 | endif 112 | let l:k_v = split(l:g) 113 | if l:k_v[0] == ':foreground' 114 | let l:gui_color = '' 115 | let l:found_gui_color = 0 116 | for l:color in split(l:k_v[1], ',') 117 | if l:color =~ '^#' 118 | let l:found_gui_color = 1 119 | let l:res_faces = l:res_faces . ' guifg=' . l:color 120 | elseif l:color != '' 121 | let l:gui_color = l:color 122 | let l:res_faces = l:res_faces . ' ctermfg=' . l:color 123 | endif 124 | endfor 125 | if ! l:found_gui_color && l:gui_color != '' 126 | let l:res_faces = l:res_faces . ' guifg=' . l:gui_color 127 | endif 128 | elseif l:k_v[0] == ':background' 129 | let l:gui_color = '' 130 | let l:found_gui_color = 0 131 | for l:color in split(l:k_v[1], ',') 132 | if l:color =~ '^#' 133 | let l:found_gui_color = 1 134 | let l:res_faces = l:res_faces . ' guibg=' . l:color 135 | elseif l:color != '' 136 | let l:gui_color = l:color 137 | let l:res_faces = l:res_faces . ' ctermbg=' . l:color 138 | endif 139 | endfor 140 | if ! l:found_gui_color && l:gui_color != '' 141 | let l:res_faces = l:res_faces . ' guibg=' . l:gui_color 142 | endif 143 | elseif l:k_v[0] == ':weight' || l:k_v[0] == ':slant' || l:k_v[0] == ':decoration' 144 | if index(l:style, l:k_v[1]) == -1 145 | call add(l:style, l:k_v[1]) 146 | endif 147 | endif 148 | elseif type(l:g) == 1 149 | " TODO emacs interprets the color and automatically determines 150 | " whether it should be set as foreground or background color 151 | let l:res_faces = l:res_faces . ' ctermfg=' . l:k_v[1] . ' guifg=' . l:k_v[1] 152 | endif 153 | endfor 154 | endfor 155 | let l:s = '' 156 | for l:i in l:style 157 | if l:s == '' 158 | let l:s = l:i 159 | else 160 | let l:s = l:s . ','. l:i 161 | endif 162 | endfor 163 | if l:s != '' 164 | let l:res_faces = l:res_faces . ' term=' . l:s . ' cterm=' . l:s . ' gui=' . l:s 165 | endif 166 | elseif type(a:faces) == 1 167 | " TODO emacs interprets the color and automatically determines 168 | " whether it should be set as foreground or background color 169 | let l:res_faces = l:res_faces . ' ctermfg=' . a:faces . ' guifg=' . a:faces 170 | endif 171 | return l:res_faces 172 | endfunction 173 | 174 | function! s:ReadTodoKeywords(keywords, todo_headings) 175 | let l:default_group = 'Todo' 176 | for l:i in a:keywords 177 | if type(l:i) == 3 178 | call s:ReadTodoKeywords(l:i, a:todo_headings) 179 | continue 180 | endif 181 | if l:i == '|' 182 | let l:default_group = 'Question' 183 | continue 184 | endif 185 | " strip access key 186 | let l:_i = substitute(l:i, "\(.*$", "", "") 187 | 188 | let l:group = l:default_group 189 | for l:j in g:org_todo_keyword_faces 190 | if l:j[0] == l:_i 191 | let l:group = 'org_todo_keyword_face_' . l:_i 192 | call OrgExtendHighlightingGroup(l:default_group, l:group, OrgInterpretFaces(l:j[1])) 193 | break 194 | endif 195 | endfor 196 | exec 'syntax match org_todo_keyword_' . l:_i . ' /\*\{1,\}\s\{1,\}\zs' . l:_i .'\(\s\|$\)/ ' . a:todo_headings 197 | exec 'hi def link org_todo_keyword_' . l:_i . ' ' . l:group 198 | endfor 199 | endfunction 200 | endif 201 | 202 | call s:ReadTodoKeywords(g:org_todo_keywords, s:todo_headings) 203 | unlet! s:todo_headings 204 | 205 | " Timestamps: {{{1 206 | "<2003-09-16 Tue> 207 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a>\)/ 208 | "<2003-09-16 Tue 12:00> 209 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d>\)/ 210 | "<2003-09-16 Tue 12:00-12:30> 211 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d-\d\d:\d\d>\)/ 212 | 213 | "<2003-09-16 Tue>--<2003-09-16 Tue> 214 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a>--<\d\d\d\d-\d\d-\d\d \a\a\a>\)/ 215 | "<2003-09-16 Tue 12:00>--<2003-09-16 Tue 12:00> 216 | syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d>--<\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d>\)/ 217 | 218 | syn match org_timestamp /\(<%%(diary-float.\+>\)/ 219 | 220 | "[2003-09-16 Tue] 221 | syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \a\a\a\]\)/ 222 | "[2003-09-16 Tue 12:00] 223 | syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d\]\)/ 224 | 225 | "[2003-09-16 Tue]--[2003-09-16 Tue] 226 | syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \a\a\a\]--\[\d\d\d\d-\d\d-\d\d \a\a\a\]\)/ 227 | "[2003-09-16 Tue 12:00]--[2003-09-16 Tue 12:00] 228 | syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d\]--\[\d\d\d\d-\d\d-\d\d \a\a\a \d\d:\d\d\]\)/ 229 | 230 | syn match org_timestamp_inactive /\(\[%%(diary-float.\+\]\)/ 231 | 232 | hi def link org_timestamp PreProc 233 | hi def link org_timestamp_inactive Comment 234 | 235 | " Deadline And Schedule: {{{1 236 | syn match org_deadline_scheduled /^\s*\(DEADLINE\|SCHEDULED\):/ 237 | hi def link org_deadline_scheduled PreProc 238 | 239 | " Tables: {{{1 240 | syn match org_table /^\s*|.*/ contains=org_timestamp,org_timestamp_inactive,hyperlink,org_table_separator,org_table_horizontal_line 241 | syn match org_table_separator /\(^\s*|[-+]\+|\?\||\)/ contained 242 | hi def link org_table_separator Type 243 | 244 | " Hyperlinks: {{{1 245 | syntax match hyperlink "\[\{2}[^][]*\(\]\[[^][]*\)\?\]\{2}" contains=hyperlinkBracketsLeft,hyperlinkURL,hyperlinkBracketsRight containedin=ALL 246 | syntax match hyperlinkBracketsLeft contained "\[\{2}" conceal 247 | syntax match hyperlinkURL contained "[^][]*\]\[" conceal 248 | syntax match hyperlinkBracketsRight contained "\]\{2}" conceal 249 | hi def link hyperlink Underlined 250 | 251 | " Comments: {{{1 252 | syntax match org_comment /^#.*/ 253 | hi def link org_comment Comment 254 | 255 | " Bullet Lists: {{{1 256 | " Ordered Lists: 257 | " 1. list item 258 | " 1) list item 259 | " a. list item 260 | " a) list item 261 | syn match org_list_ordered "^\s*\(\a\|\d\+\)[.)]\(\s\|$\)" nextgroup=org_list_item 262 | hi def link org_list_ordered Identifier 263 | 264 | " Unordered Lists: 265 | " - list item 266 | " * list item 267 | " + list item 268 | " + and - don't need a whitespace prefix 269 | syn match org_list_unordered "^\(\s*[-+]\|\s\+\*\)\(\s\|$\)" nextgroup=org_list_item 270 | hi def link org_list_unordered Identifier 271 | 272 | " Definition Lists: 273 | " - Term :: expl. 274 | " 1) Term :: expl. 275 | syntax match org_list_def /.*\s\+::/ contained 276 | hi def link org_list_def PreProc 277 | 278 | syntax match org_list_item /.*$/ contained contains=org_subtask_percent,org_subtask_number,org_subtask_percent_100,org_subtask_number_all,org_list_checkbox,org_bold,org_italic,org_underline,org_code,org_verbatim,org_timestamp,org_timestamp_inactive,org_list_def 279 | syntax match org_list_checkbox /\[[ X-]]/ contained 280 | hi def link org_list_bullet Identifier 281 | hi def link org_list_checkbox PreProc 282 | 283 | " Block Delimiters: {{{1 284 | syntax case ignore 285 | syntax match org_block_delimiter /^#+BEGIN_.*/ 286 | syntax match org_block_delimiter /^#+END_.*/ 287 | syntax match org_key_identifier /^#+[^ ]*:/ 288 | syntax match org_title /^#+TITLE:.*/ contains=org_key_identifier 289 | hi def link org_block_delimiter Comment 290 | hi def link org_key_identifier Comment 291 | hi def link org_title Title 292 | 293 | " Block Markup: {{{1 294 | " we consider all BEGIN/END sections as 'verbatim' blocks (inc. 'quote', 'verse', 'center') 295 | " except 'example' and 'src' which are treated as 'code' blocks. 296 | " Note: the non-standard '>' prefix is supported for quotation lines. 297 | " Note: the '^:.*" rule must be defined before the ':PROPERTIES:' one below. 298 | " TODO: http://vim.wikia.com/wiki/Different_syntax_highlighting_within_regions_of_a_file 299 | syntax match org_verbatim /^\s*>.*/ 300 | syntax match org_code /^\s*:.*/ 301 | syntax region org_verbatim start="^\s*#+BEGIN_.*" end="^\s*#+END_.*" keepend contains=org_block_delimiter 302 | syntax region org_code start="^\s*#+BEGIN_SRC" end="^\s*#+END_SRC" keepend contains=org_block_delimiter 303 | syntax region org_code start="^\s*#+BEGIN_EXAMPLE" end="^\s*#+END_EXAMPLE" keepend contains=org_block_delimiter 304 | hi def link org_code String 305 | hi def link org_verbatim String 306 | 307 | " Properties: {{{1 308 | syn region Error matchgroup=org_properties_delimiter start=/^\s*:PROPERTIES:\s*$/ end=/^\s*:END:\s*$/ contains=org_property keepend 309 | syn match org_property /^\s*:[^\t :]\+:\s\+[^\t ]/ contained contains=org_property_value 310 | syn match org_property_value /:\s\zs.*/ contained 311 | hi def link org_properties_delimiter PreProc 312 | hi def link org_property Statement 313 | hi def link org_property_value Constant 314 | " Break down subtasks 315 | syntax match org_subtask_number /\[\d*\/\d*]/ contained 316 | syntax match org_subtask_percent /\[\d*%\]/ contained 317 | syntax match org_subtask_number_all /\[\(\d\+\)\/\1\]/ contained 318 | syntax match org_subtask_percent_100 /\[100%\]/ contained 319 | 320 | hi def link org_subtask_number String 321 | hi def link org_subtask_percent String 322 | hi def link org_subtask_percent_100 Identifier 323 | hi def link org_subtask_number_all Identifier 324 | 325 | " Plugin SyntaxRange: {{{1 326 | " This only works if you have SyntaxRange installed: 327 | " https://github.com/vim-scripts/SyntaxRange 328 | 329 | " BEGIN_SRC 330 | if exists('g:loaded_SyntaxRange') 331 | call SyntaxRange#Include('#+BEGIN_SRC\ vim', '#+END_SRC', 'vim', 'comment') 332 | call SyntaxRange#Include('#+BEGIN_SRC\ python', '#+END_SRC', 'python', 'comment') 333 | call SyntaxRange#Include('#+BEGIN_SRC\ c', '#+END_SRC', 'c', 'comment') 334 | " cpp must be below c, otherwise you get c syntax hl for cpp files 335 | call SyntaxRange#Include('#+BEGIN_SRC\ cpp', '#+END_SRC', 'cpp', 'comment') 336 | call SyntaxRange#Include('#+BEGIN_SRC\ ruby', '#+END_SRC', 'ruby', 'comment') 337 | " call SyntaxRange#Include('#+BEGIN_SRC\ lua', '#+END_SRC', 'lua', 'comment') 338 | " call SyntaxRange#Include('#+BEGIN_SRC\ lisp', '#+END_SRC', 'lisp', 'comment') 339 | 340 | " LaTeX 341 | call SyntaxRange#Include('\\begin[.*]{.*}', '\\end{.*}', 'tex') 342 | call SyntaxRange#Include('\\begin{.*}', '\\end{.*}', 'tex') 343 | call SyntaxRange#Include('\\\[', '\\\]', 'tex') 344 | endif 345 | 346 | " vi: ft=vim:tw=80:sw=4:ts=4:fdm=marker 347 | -------------------------------------------------------------------------------- /ftplugin/orgmode/vimbuffer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | vimbuffer 5 | ~~~~~~~~~~ 6 | 7 | VimBuffer and VimBufferContent are the interface between liborgmode and 8 | vim. 9 | 10 | VimBuffer extends the liborgmode.document.Document(). 11 | Document() is just a general implementation for loading an org file. It 12 | has no interface to an actual file or vim buffer. This is the task of 13 | vimbuffer.VimBuffer(). It is the interfaces to vim. The main tasks for 14 | VimBuffer are to provide read and write access to a real vim buffer. 15 | 16 | VimBufferContent is a helper class for VimBuffer. Basically, it hides the 17 | details of encoding - everything read from or written to VimBufferContent 18 | is UTF-8. 19 | """ 20 | 21 | from UserList import UserList 22 | 23 | import vim 24 | 25 | from orgmode import settings 26 | from orgmode.exceptions import BufferNotFound, BufferNotInSync 27 | from orgmode.liborgmode.documents import Document, MultiPurposeList, Direction 28 | from orgmode.liborgmode.headings import Heading 29 | 30 | 31 | class VimBuffer(Document): 32 | def __init__(self, bufnr=0): 33 | u""" 34 | :bufnr: 0: current buffer, every other number refers to another buffer 35 | """ 36 | Document.__init__(self) 37 | self._bufnr = vim.current.buffer.number if bufnr == 0 else bufnr 38 | self._changedtick = -1 39 | self._cached_heading = None 40 | 41 | if self._bufnr == vim.current.buffer.number: 42 | self._content = VimBufferContent(vim.current.buffer) 43 | else: 44 | _buffer = None 45 | for b in vim.buffers: 46 | if self._bufnr == b.number: 47 | _buffer = b 48 | break 49 | 50 | if not _buffer: 51 | raise BufferNotFound(u'Unable to locate buffer number #%d' % self._bufnr) 52 | self._content = VimBufferContent(_buffer) 53 | 54 | self.update_changedtick() 55 | self._orig_changedtick = self._changedtick 56 | 57 | @property 58 | def tabstop(self): 59 | return int(vim.eval(u'&ts'.encode(u'utf-8'))) 60 | 61 | @property 62 | def tag_column(self): 63 | return int(settings.get(u'org_tag_column', u'77')) 64 | 65 | @property 66 | def is_insync(self): 67 | if self._changedtick == self._orig_changedtick: 68 | self.update_changedtick() 69 | return self._changedtick == self._orig_changedtick 70 | 71 | @property 72 | def bufnr(self): 73 | u""" 74 | :returns: The buffer's number for the current document 75 | """ 76 | return self._bufnr 77 | 78 | def changedtick(): 79 | u""" Number of changes in vimbuffer """ 80 | def fget(self): 81 | return self._changedtick 82 | def fset(self, value): 83 | self._changedtick = value 84 | return locals() 85 | changedtick = property(**changedtick()) 86 | 87 | def get_todo_states(self, strip_access_key=True): 88 | u""" Returns a list containing a tuple of two lists of allowed todo 89 | states split by todo and done states. Multiple todo-done state 90 | sequences can be defined. 91 | 92 | :returns: [([todo states], [done states]), ..] 93 | """ 94 | states = settings.get(u'org_todo_keywords', []) 95 | if type(states) not in (list, tuple): 96 | return [] 97 | 98 | def parse_states(s, stop=0): 99 | res = [] 100 | if not s: 101 | return res 102 | if type(s[0]) in (unicode, str): 103 | r = [] 104 | for i in s: 105 | _i = i 106 | if type(_i) == str: 107 | _i = _i.decode(u'utf-8') 108 | if type(_i) == unicode and _i: 109 | if strip_access_key and u'(' in _i: 110 | _i = _i[:_i.index(u'(')] 111 | if _i: 112 | r.append(_i) 113 | else: 114 | r.append(_i) 115 | if not u'|' in r: 116 | if not stop: 117 | res.append((r[:-1], [r[-1]])) 118 | else: 119 | res = (r[:-1], [r[-1]]) 120 | else: 121 | seperator_pos = r.index(u'|') 122 | if not stop: 123 | res.append((r[0:seperator_pos], r[seperator_pos + 1:])) 124 | else: 125 | res = (r[0:seperator_pos], r[seperator_pos + 1:]) 126 | elif type(s) in (list, tuple) and not stop: 127 | for i in s: 128 | r = parse_states(i, stop=1) 129 | if r: 130 | res.append(r) 131 | return res 132 | 133 | return parse_states(states) 134 | 135 | def update_changedtick(self): 136 | if self.bufnr == vim.current.buffer.number: 137 | self._changedtick = int(vim.eval(u'b:changedtick'.encode(u'utf-8'))) 138 | else: 139 | vim.command(u'unlet! g:org_changedtick | let g:org_lz = &lz | let g:org_hidden = &hidden | set lz hidden'.encode(u'utf-8')) 140 | # TODO is this likely to fail? maybe some error hangling should be added 141 | vim.command((u'keepalt buffer %d | let g:org_changedtick = b:changedtick | buffer %d' % \ 142 | (self.bufnr, vim.current.buffer.number)).encode(u'utf-8')) 143 | vim.command(u'let &lz = g:org_lz | let &hidden = g:org_hidden | unlet! g:org_lz g:org_hidden | redraw'.encode(u'utf-8')) 144 | self._changedtick = int(vim.eval(u'g:org_changedtick'.encode(u'utf-8'))) 145 | 146 | def write(self): 147 | u""" write the changes to the vim buffer 148 | 149 | :returns: True if something was written, otherwise False 150 | """ 151 | if not self.is_dirty: 152 | return False 153 | 154 | self.update_changedtick() 155 | if not self.is_insync: 156 | raise BufferNotInSync(u'Buffer is not in sync with vim!') 157 | 158 | # write meta information 159 | if self.is_dirty_meta_information: 160 | meta_end = 0 if self._orig_meta_information_len is None else self._orig_meta_information_len 161 | self._content[:meta_end] = self.meta_information 162 | self._orig_meta_information_len = len(self.meta_information) 163 | 164 | # remove deleted headings 165 | already_deleted = [] 166 | for h in sorted(self._deleted_headings, cmp=lambda x, y: cmp(x._orig_start, y._orig_start), reverse=True): 167 | if h._orig_start is not None and h._orig_start not in already_deleted: 168 | # this is a heading that actually exists on the buffer and it 169 | # needs to be removed 170 | del self._content[h._orig_start:h._orig_start + h._orig_len] 171 | already_deleted.append(h._orig_start) 172 | del self._deleted_headings[:] 173 | del already_deleted 174 | 175 | # update changed headings and add new headings 176 | for h in self.all_headings(): 177 | if h.is_dirty: 178 | if h._orig_start is not None: 179 | # this is a heading that existed before and was changed. It 180 | # needs to be replaced 181 | if h.is_dirty_heading: 182 | self._content[h.start:h.start + 1] = [unicode(h)] 183 | if h.is_dirty_body: 184 | self._content[h.start + 1:h.start + h._orig_len] = h.body 185 | else: 186 | # this is a new heading. It needs to be inserted 187 | self._content[h.start:h.start] = [unicode(h)] + h.body 188 | h._dirty_heading = False 189 | h._dirty_body = False 190 | # for all headings the length and start offset needs to be updated 191 | h._orig_start = h.start 192 | h._orig_len = len(h) 193 | 194 | self._dirty_meta_information = False 195 | self._dirty_document = False 196 | 197 | self.update_changedtick() 198 | self._orig_changedtick = self._changedtick 199 | return True 200 | 201 | def write_heading(self, heading, including_children=True): 202 | """ WARNING: use this function only when you know what you are doing! 203 | This function writes a heading to the vim buffer. It offers performance 204 | advantages over the regular write() function. This advantage is 205 | combined with no sanity checks! Whenever you use this function, make 206 | sure the heading you are writing contains the right offsets 207 | (Heading._orig_start, Heading._orig_len). 208 | 209 | Usage example: 210 | # Retrieve a potentially dirty document 211 | d = ORGMODE.get_document(allow_dirty=True) 212 | # Don't rely on the DOM, retrieve the heading afresh 213 | h = d.find_heading(direction=Direction.FORWARD, position=100) 214 | # Update tags 215 | h.tags = ['tag1', 'tag2'] 216 | # Write the heading 217 | d.write_heading(h) 218 | 219 | This function can't be used to delete a heading! 220 | 221 | :heading: Write this heading with to the vim buffer 222 | :including_children: Also include children in the update 223 | 224 | :returns The written heading 225 | """ 226 | if including_children and heading.children: 227 | for child in heading.children[::-1]: 228 | self.write_heading(child, including_children) 229 | 230 | if heading.is_dirty: 231 | if heading._orig_start is not None: 232 | # this is a heading that existed before and was changed. It 233 | # needs to be replaced 234 | if heading.is_dirty_heading: 235 | self._content[heading._orig_start:heading._orig_start + 1] = [unicode(heading)] 236 | if heading.is_dirty_body: 237 | self._content[heading._orig_start + 1:heading._orig_start + heading._orig_len] = heading.body 238 | else: 239 | # this is a new heading. It needs to be inserted 240 | raise ValueError('Heading must contain the attribute _orig_start! %s' % heading) 241 | heading._dirty_heading = False 242 | heading._dirty_body = False 243 | # for all headings the length offset needs to be updated 244 | heading._orig_len = len(heading) 245 | 246 | return heading 247 | 248 | def write_checkbox(self, checkbox, including_children=True): 249 | if including_children and checkbox.children: 250 | for child in checkbox.children[::-1]: 251 | self.write_checkbox(child, including_children) 252 | 253 | if checkbox.is_dirty: 254 | if checkbox._orig_start is not None: 255 | # this is a heading that existed before and was changed. It 256 | # needs to be replaced 257 | # print "checkbox is dirty? " + str(checkbox.is_dirty_checkbox) 258 | # print checkbox 259 | if checkbox.is_dirty_checkbox: 260 | self._content[checkbox._orig_start:checkbox._orig_start + 1] = [unicode(checkbox)] 261 | if checkbox.is_dirty_body: 262 | self._content[checkbox._orig_start + 1:checkbox._orig_start + checkbox._orig_len] = checkbox.body 263 | else: 264 | # this is a new checkbox. It needs to be inserted 265 | raise ValueError('Checkbox must contain the attribute _orig_start! %s' % checkbox) 266 | checkbox._dirty_checkbox = False 267 | checkbox._dirty_body = False 268 | # for all headings the length offset needs to be updated 269 | checkbox._orig_len = len(checkbox) 270 | 271 | return checkbox 272 | 273 | def write_checkboxes(self, checkboxes): 274 | pass 275 | 276 | def previous_heading(self, position=None): 277 | u""" Find the next heading (search forward) and return the related object 278 | :returns: Heading object or None 279 | """ 280 | h = self.current_heading(position=position) 281 | if h: 282 | return h.previous_heading 283 | 284 | def current_heading(self, position=None): 285 | u""" Find the current heading (search backward) and return the related object 286 | :returns: Heading object or None 287 | """ 288 | if position is None: 289 | position = vim.current.window.cursor[0] - 1 290 | 291 | if not self.headings: 292 | return 293 | 294 | def binaryFindInDocument(): 295 | hi = len(self.headings) 296 | lo = 0 297 | while lo < hi: 298 | mid = (lo+hi)//2 299 | h = self.headings[mid] 300 | if h.end_of_last_child < position: 301 | lo = mid + 1 302 | elif h.start > position: 303 | hi = mid 304 | else: 305 | return binaryFindHeading(h) 306 | 307 | def binaryFindHeading(heading): 308 | if not heading.children or heading.end >= position: 309 | return heading 310 | 311 | hi = len(heading.children) 312 | lo = 0 313 | while lo < hi: 314 | mid = (lo+hi)//2 315 | h = heading.children[mid] 316 | if h.end_of_last_child < position: 317 | lo = mid + 1 318 | elif h.start > position: 319 | hi = mid 320 | else: 321 | return binaryFindHeading(h) 322 | 323 | # look at the cache to find the heading 324 | h_tmp = self._cached_heading 325 | if h_tmp is not None: 326 | if h_tmp.end_of_last_child > position and \ 327 | h_tmp.start < position: 328 | if h_tmp.end < position: 329 | self._cached_heading = binaryFindHeading(h_tmp) 330 | return self._cached_heading 331 | 332 | self._cached_heading = binaryFindInDocument() 333 | return self._cached_heading 334 | 335 | def next_heading(self, position=None): 336 | u""" Find the next heading (search forward) and return the related object 337 | :returns: Heading object or None 338 | """ 339 | h = self.current_heading(position=position) 340 | if h: 341 | return h.next_heading 342 | 343 | def find_current_heading(self, position=None, heading=Heading): 344 | u""" Find the next heading backwards from the position of the cursor. 345 | The difference to the function current_heading is that the returned 346 | object is not built into the DOM. In case the DOM doesn't exist or is 347 | out of sync this function is much faster in fetching the current 348 | heading. 349 | 350 | :position: The position to start the search from 351 | 352 | :heading: The base class for the returned heading 353 | 354 | :returns: Heading object or None 355 | """ 356 | return self.find_heading(vim.current.window.cursor[0] - 1 \ 357 | if position is None else position, \ 358 | direction=Direction.BACKWARD, heading=heading, \ 359 | connect_with_document=False) 360 | 361 | 362 | class VimBufferContent(MultiPurposeList): 363 | u""" Vim Buffer Content is a UTF-8 wrapper around a vim buffer. When 364 | retrieving or setting items in the buffer an automatic conversion is 365 | performed. 366 | 367 | This ensures UTF-8 usage on the side of liborgmode and the vim plugin 368 | vim-orgmode. 369 | """ 370 | 371 | def __init__(self, vimbuffer, on_change=None): 372 | MultiPurposeList.__init__(self, on_change=on_change) 373 | 374 | # replace data with vimbuffer to make operations change the actual 375 | # buffer 376 | self.data = vimbuffer 377 | 378 | def __contains__(self, item): 379 | i = item 380 | if type(i) is unicode: 381 | i = item.encode(u'utf-8') 382 | return MultiPurposeList.__contains__(self, i) 383 | 384 | def __getitem__(self, i): 385 | item = MultiPurposeList.__getitem__(self, i) 386 | if type(item) is str: 387 | return item.decode(u'utf-8') 388 | return item 389 | 390 | def __getslice__(self, i, j): 391 | return [item.decode(u'utf-8') if type(item) is str else item \ 392 | for item in MultiPurposeList.__getslice__(self, i, j)] 393 | 394 | def __setitem__(self, i, item): 395 | _i = item 396 | if type(_i) is unicode: 397 | _i = item.encode(u'utf-8') 398 | 399 | MultiPurposeList.__setitem__(self, i, _i) 400 | 401 | def __setslice__(self, i, j, other): 402 | o = [] 403 | o_tmp = other 404 | if type(o_tmp) not in (list, tuple) and not isinstance(o_tmp, UserList): 405 | o_tmp = list(o_tmp) 406 | for item in o_tmp: 407 | if type(item) == unicode: 408 | o.append(item.encode(u'utf-8')) 409 | else: 410 | o.append(item) 411 | MultiPurposeList.__setslice__(self, i, j, o) 412 | 413 | def __add__(self, other): 414 | raise NotImplementedError() 415 | # TODO: implement me 416 | if isinstance(other, UserList): 417 | return self.__class__(self.data + other.data) 418 | elif isinstance(other, type(self.data)): 419 | return self.__class__(self.data + other) 420 | else: 421 | return self.__class__(self.data + list(other)) 422 | 423 | def __radd__(self, other): 424 | raise NotImplementedError() 425 | # TODO: implement me 426 | if isinstance(other, UserList): 427 | return self.__class__(other.data + self.data) 428 | elif isinstance(other, type(self.data)): 429 | return self.__class__(other + self.data) 430 | else: 431 | return self.__class__(list(other) + self.data) 432 | 433 | def __iadd__(self, other): 434 | o = [] 435 | o_tmp = other 436 | if type(o_tmp) not in (list, tuple) and not isinstance(o_tmp, UserList): 437 | o_tmp = list(o_tmp) 438 | for i in o_tmp: 439 | if type(i) is unicode: 440 | o.append(i.encode(u'utf-8')) 441 | else: 442 | o.append(i) 443 | 444 | return MultiPurposeList.__iadd__(self, o) 445 | 446 | def append(self, item): 447 | i = item 448 | if type(item) is str: 449 | i = item.encode(u'utf-8') 450 | MultiPurposeList.append(self, i) 451 | 452 | def insert(self, i, item): 453 | _i = item 454 | if type(_i) is str: 455 | _i = item.encode(u'utf-8') 456 | MultiPurposeList.insert(self, i, _i) 457 | 458 | def index(self, item, *args): 459 | i = item 460 | if type(i) is unicode: 461 | i = item.encode(u'utf-8') 462 | MultiPurposeList.index(self, i, *args) 463 | 464 | def pop(self, i=-1): 465 | return MultiPurposeList.pop(self, i).decode(u'utf-8') 466 | 467 | def extend(self, other): 468 | o = [] 469 | o_tmp = other 470 | if type(o_tmp) not in (list, tuple) and not isinstance(o_tmp, UserList): 471 | o_tmp = list(o_tmp) 472 | for i in o_tmp: 473 | if type(i) is unicode: 474 | o.append(i.encode(u'utf-8')) 475 | else: 476 | o.append(i) 477 | MultiPurposeList.extend(self, o) 478 | 479 | 480 | # vim: set noexpandtab: 481 | --------------------------------------------------------------------------------