Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Added support for extracting source code blocks (tangling) #193

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
list of the already supported features:

- Proper syntax highlighting
- Cycle visibility of headings (foliding)
- Cycle visibility of headings (folding)
- Navigate between headings
- Edit the structure of the document: add, move, promote, denote headings
and more
Expand Down
62 changes: 61 additions & 1 deletion doc/orgguide.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,67 @@ PUBLISHING *orgguide-publishing*
==============================================================================
WORKING WITH SOURCE CODE *orgguide-source*

Not yet implemented in vim-orgmode~

NOTE: vim-orgmode relies on Emacs for this feature. Emacs _and_ Emacs'
org-mode need to be installed!

------------------------------------------------------------------------------
Extracting Source Code~
*orgguide-source-extract*

vim-orgmode allows to extract ("tangle") source blocks, that is, to extract source code
from your org-file and write them into file(s) on your filesystem. This
actually uses emacs in the background, meaning that the behavior of
vim-orgmode is identical with emacs org-mode! One implication of this is that
"noweb" syntax can be used to re-order code blocks in your org document. For
more details, please see the org-manual Section 14.10 (available at
http://orgmode.org/manual/Noweb-reference-syntax.html#Noweb-reference-syntax)
and the noweb reference: http://www.cs.tufts.edu/~nr/noweb/

Currently, the following commands are supported:
>
:OrgBabelTangle
:OrgBabelTangleFile
<
While the first command simply tangles the current file, the second
command prompts for a filename to be entered; this file will then be tangled.

Here is an example of a code block with the tangle header:

>
#+BEGIN_SRC sh :tangle filename
print -- "This Works!"
#+END_SRC
<
You can explicitly exclude a code block from being tangled (though this is the
default):

>
#+BEGIN_SRC sh :tangle no
print -- "Excluded from tangling!"
#+END_SRC
<

Another way to mark a block for tangling is the following:
>
#+BEGIN_SRC sh :tangle yes
print -- "Included"
#+END_SRC
<
In the last example, the output will be written to org-file-name.sh, as the
filename will be the filename of your orgfile with the ".org"-extension being
replaced by the language name (here: "sh").
------------------------------------------------------------------------------
Keybindings~

*orgguide-<LocalLeader>cvt*
<LocalLeader>cvt Call :OrgBabelTangle -- this tangles the current file
only

*orgguide-<LocalLeader>cvf*
<LocalLeader>cvf Call :OrgBabelTangleFile -- prompts for a filename to
be entered and attempts to tangle this file.
Unfortunately, there is no autocompletion available...

==============================================================================
MISCELLANEOUS *orgguide-misc*
Expand Down
2 changes: 1 addition & 1 deletion ftplugin/org.vim
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ let g:loaded_org = 1

" Default org plugins that will be loaded (in the given order)
if ! exists('g:org_plugins') && ! exists('b:org_plugins')
let g:org_plugins = ['ShowHide', '|', 'Navigator', 'EditStructure', 'EditCheckbox', '|', 'Hyperlinks', '|', 'Todo', 'TagsProperties', 'Date', 'Agenda', 'Misc', '|', 'Export']
let g:org_plugins = ['ShowHide', '|', 'Navigator', 'EditStructure', 'EditCheckbox', '|', 'Hyperlinks', '|', 'Todo', 'TagsProperties', 'Date', 'Agenda', 'Misc', '|', 'Export', '|', 'BabelTangle']
endif

if ! exists('g:org_syntax_highlight_leading_stars') && ! exists('b:org_syntax_highlight_leading_stars')
Expand Down
122 changes: 122 additions & 0 deletions ftplugin/orgmode/plugins/BabelTangle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-

import os
import subprocess

import vim

from orgmode._vim import ORGMODE, echoe, echom, get_user_input
from orgmode.menu import Submenu, ActionEntry, add_cmd_mapping_menu
from orgmode.keybinding import Keybinding, Plug, Command
from orgmode import settings


class BabelTangle(object):
u"""
Tangle source code blocks using emacs.
"""

def __init__(self):
u""" Initialize plugin """
object.__init__(self)
# menu entries this plugin should create
self.menu = ORGMODE.orgmenu + Submenu(u'Babel')

# key bindings for this plugin
# key bindings are also registered through the menu so only additional
# bindings should be put in this variable
self.keybindings = []

# commands for this plugin
self.commands = []

@classmethod
def _get_init_script(cls):
init_script = settings.get(u'org_export_init_script', u'')
if init_script:
init_script = os.path.expandvars(os.path.expanduser(init_script))
if os.path.exists(init_script):
return init_script
else:
echoe(u'Unable to find init script %s' % init_script)

@classmethod
def _callFunction(cls, function):
"""Export current file to format_.

:format_: elisp function to call
:returns: return code
"""
emacsbin = os.path.expandvars(os.path.expanduser(
settings.get(u'org_export_emacs', u'/usr/bin/emacs')))
if not os.path.exists(emacsbin):
echoe(u'Unable to find emacs binary %s' % emacsbin)

# build the export command
cmd = [
emacsbin,
u'-nw',
u'--batch',
u'--visit=%s' % vim.eval(u'expand("%:p")'),
u'--execute=%s' % function
]
# source init script as well
init_script = cls._get_init_script()
if init_script:
cmd.extend(['--script', init_script])

# export
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()

if p.returncode != 0 or settings.get(u'org_export_verbose') == 1:
echom('\n'.join(p.communicate()))
return p.returncode

@classmethod
def tangle(cls):
u"""Tangle all codeblocks of the current buffer"""
ret = cls._callFunction(u'(funcall \'org-babel-tangle)')
if ret != 0:
echoe('Could not tangle file; make sure org-babel-tangle is callable from within emacs.')
else:
echom(u'Tangling successful')

@classmethod
def tangleFile(cls):
u"""Tangle all codeblocks of the specified filename"""
msg = u'Specify filename (relative to current path)'
filename = get_user_input(msg)
ret = cls._callFunction(u'(org-babel-tangle-file "'+filename+'")')
if ret != 0:
echoe('Could not tangle file; make sure org-babel-tangle-file is callable from within emacs.')
else:
echom(u' Successfully tangled file ' + filename + '!')

def register(self):
u"""Registration and keybindings."""

# path to emacs executable
settings.set(u'org_export_emacs', u'/usr/bin/emacs')
# verbose output for export
settings.set(u'org_export_verbose', 0)
# allow the user to define an initialization script
settings.set(u'org_export_init_script', u'')

add_cmd_mapping_menu(
self,
name=u'OrgBabelTangle',
function=u':py ORGMODE.plugins[u"BabelTangle"].tangle()<CR>',
key_mapping=u'<localleader>cvt',
menu_desrc=u'Tangle file (via Emacs)'
)

add_cmd_mapping_menu(
self,
name=u'OrgBabelTangleFile',
function=u':py ORGMODE.plugins[u"BabelTangle"].tangleFile()<CR>',
key_mapping=u'<localleader>cvf',
menu_desrc=u'Tangle file (via Emacs)'
)

# vim: set noexpandtab:
6 changes: 3 additions & 3 deletions syntax/org.vim
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,9 @@ hi def link org_title Title
" TODO: http://vim.wikia.com/wiki/Different_syntax_highlighting_within_regions_of_a_file
syntax match org_verbatim /^\s*>.*/
syntax match org_code /^\s*:.*/
syntax region org_verbatim start="^#+BEGIN_.*" end="^#+END_.*" keepend contains=org_block_delimiter
syntax region org_code start="^#+BEGIN_SRC" end="^#+END_SRC" keepend contains=org_block_delimiter
syntax region org_code start="^#+BEGIN_EXAMPLE" end="^#+END_EXAMPLE" keepend contains=org_block_delimiter
syntax region org_verbatim start="^\s*#+BEGIN_.*" end="^\s*#+END_.*" keepend contains=org_block_delimiter
syntax region org_code start="^\s*#+BEGIN_SRC" end="^\s*#+END_SRC" keepend contains=org_block_delimiter
syntax region org_code start="^\s*#+BEGIN_EXAMPLE" end="^\s*#+END_EXAMPLE" keepend contains=org_block_delimiter
hi def link org_code String
hi def link org_verbatim String

Expand Down
17 changes: 10 additions & 7 deletions tests/orgmode_testfile.org
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@

* SRC blocks
#+BEGIN_SRC sh :tangle tmp_tangled_file
print -- "Yes!"
#+END_SRC
* bold, italics and underline syntax matching
** Should match:

*foo* *foo*
*foo* *foo*
*Really, quite long sentence*.
_foo_ _foo_
_foo_ _foo_
_really, quite long sentence._.

*Übermensch á* *eä* *ý€*
*Übermensch á* *eä* *ý€*
_Ÿ ï_

*sdf l.*
*sdfsdf ,.*
*foo_ sdf /*
*sdfsdf ,.*
*foo_ sdf /*
/sdf sdf sdf ./

/google.com/
Expand All @@ -25,7 +28,7 @@ _really, quite long sentence._.
** Should not match
http://google.com/
//google.com/
* sdf* _ sdf_
* sdf* _ sdf_
*sdfsdf sdf,*
*foo *
foo_not underlined_bar
Expand Down
2 changes: 2 additions & 0 deletions tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import test_liborgdatetime
import test_liborgtimerange

import test_plugin_babel_tangle
import test_plugin_date
import test_plugin_edit_structure
import test_plugin_edit_checkbox
Expand Down Expand Up @@ -41,6 +42,7 @@
tests.addTests(test_liborgtimerange.suite())

# plugins
tests.addTests(test_plugin_babel_tangle.suite())
tests.addTests(test_plugin_date.suite())
tests.addTests(test_plugin_edit_structure.suite())
tests.addTests(test_plugin_edit_checkbox.suite())
Expand Down
44 changes: 44 additions & 0 deletions tests/test_plugin_babel_tangle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-

import unittest
import sys
sys.path.append(u'../ftplugin')

import vim
import os.path

from orgmode._vim import ORGMODE

PLUGIN_NAME = u'BabelTangle'

counter = 0

class BabelTangleTestCase(unittest.TestCase):

def setUp(self):
global counter
counter += 1
vim.CMDHISTORY = []
vim.CMDRESULTS = {}
vim.EVALHISTORY = []
vim.current.buffer[:] = [ i.encode(u'utf-8') for i in u"""
* Heading 1
This is some text, followed by a source block

#+BEGIN_SRC sh :tangle tmp_tangle_fileoutput
print -- "Everything ok!"
#+END_SRC
""".split(u'\n') ]

def test_tangling(self):
global counter
counter += 1
# test on self.c1
# update_checkboxes_status
vim.command("OrgBabelTangle")
self.assertTrue(os.path.isfile("tmp_tangle_fileoutput"))

def suite():
return unittest.TestLoader().loadTestsFromTestCase(BabelTangleTestCase)

# vim: set noexpandtab