Version 0.1 - readme, install files, and more

This commit is contained in:
Sean Yeh 2014-12-22 17:41:53 -06:00
parent 65780b1896
commit 918ea95a48
12 changed files with 621 additions and 4 deletions

22
Makefile Normal file
View File

@ -0,0 +1,22 @@
run_testing: testing
killall soffice.bin || echo "No libreoffice instance found"
lowriter "$$TESTING_ODT" --norestore &
testing: src/vibreoffice.vbs
./compile.sh "src/vibreoffice.vbs" "$$TESTING_XBA"
extension: clean src/vibreoffice.vbs
if [ -z "$$VIBREOFFICE_VERSION" ]; then \
echo "VIBREOFFICE_VERSION must be set"; \
else \
mkdir -p build; mkdir -p dist; \
cp -r extension/template build/template; \
./compile.sh "src/vibreoffice.vbs" "build/template/vibreoffice/vibreoffice.xba"; \
cd "build/template"; \
sed -i "s/%VIBREOFFICE_VERSION%/$$VIBREOFFICE_VERSION/g" description.xml; \
zip -r "../../dist/vibreoffice-$$VIBREOFFICE_VERSION.oxt" .; \
fi
.PHONY: clean
clean:
rm -rf build

View File

@ -1,10 +1,30 @@
# vibreoffice
A Vi/Vim Mode Extension for Libreoffice and OpenOffice (Apache OpenOffice, OpenOffice.org)
### Installation
Coming soon.
vibreoffice is an extension for Libreoffice and OpenOffice that brings some of
your favorite key bindings from vi/vim to your favorite office suite. It is
obviously not meant to be feature-complete, but hopefully will be useful to
both vi/vim neophytes and experts alike.
### Installation/Usage
The easiest way to install is to download the
[latest extension file](https://raw.github.com/seanyeh/vibreoffice/master/dist/vibreoffice-0.1.0.oxt)
and open it with LibreOffice/OpenOffice.
To enable/disable vibreoffice, simply select Tools -> Add-Ons -> vibreoffice.
If you really want to, you can build the .oxt file yourself by running
```shell
# replace 0.0.0 with your desired version number
VIBREOFFICE_VERSION="0.0.0" make extension
```
This will simply build the extension file from the template files in
`extension/template`. These template files were auto-generated using
[Extension Compiler](https://wiki.openoffice.org/wiki/Extensions_Packager#Download).
### Features
vibreoffice currently supports:
- Insert (`i`, `I`, `a`, `A`, `o`, `O`), Visual (`v`), Normal modes
- Movement keys: `hjkl`, `w`, `W`, `b`, `B`, `e`, `$`, `^`, `{}`, `()`, `C-d`, `C-u`
@ -14,7 +34,35 @@ vibreoffice currently supports:
- Deletion: `x`, `d`, `c`, `s`, `D`, `C`, `S`, `dd`, `cc`
- Plus movement and number modifiers: e.g. `5dw`, `c3j`, `2dfe`
- Delete a/inner block: e.g. `di(`, `da{`, `ci[`, `ci"`, `ca'`, `dit`
- More to come!
### Known differences/issues
If you are familiar with vi/vim, then vibreoffice should give very few
surprises. However, there are some differences, primarily due to word
processor-text editor differences or limitations of the LibreOffice API and/or
my patience.
- The concept of lines in a text editor is not quite analogous to that of a
word processor. I made my best effort to incorporate the line analogy while keeping
the spirit of word processing.
- Unlike vi/vim, movement keys will wrap to the next line
- Due to line wrapping, you may find your cursor move up/down a line for
commands that would otherwise leave you in the same position (such as `dd`)
- Currently, I am using LibreOffice's built-in word detection for word
movements (`w`, `W`, etc.) which differs slightly from vi's. For many
situations I find this satisfactory, but there are some funky cases involving
certain symbols. I may or may not change this in the future.
- vibreoffice does not have contextual awareness. What I mean by that is that
it does not keep track of which parentheses/braces match. Hence, you may have
unexpected behavior (using commands such as `di(`) if your document has
syntatically uneven parentheses/braces or nesting of such symbols. I don't
intend to fix this for now, as I don't believe this is a critical feature for
word processing.
- Using `d`, `c` (or any of their variants) will temporarily bring you into
Visual mode. This is intentional and should not have any noticeable effects.
vibreoffice is new, so it is bound to have plenty of bugs. Please let me know
if you run into anything!
### License
vibreoffice is released under the MIT License.

22
compile.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
#
# "Compile" BASIC macro file to LibreOffice-compatible xba file
# compile SRC DESTINATION
compile() {
# Escape XML &<>'"
src=`sed "s/\&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/'/\&apos;/g; s/\"/\&quot;/g" "$1"`
xbafile="$2"
name="`basename -s \".xba\" "$xbafile"`"
XBA_TEMPLATE='<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">\n<script:module xmlns:script="http://openoffice.org/2000/script" script:name="%s" script:language="StarBasic">\n%s\n</script:module>'
printf "$XBA_TEMPLATE" "$name" "$src" > "$xbafile"
}
if [ "$#" -ne 2 ]; then
echo "Usage: $0 SOURCE DESTINATION"
else
compile "$1" "$2"
fi

BIN
dist/vibreoffice-0.1.0.oxt vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
oor:name="Addons" oor:package="org.openoffice.Office">
<node oor:name="AddonUI">
<node oor:name="AddonMenu">
<node oor:name="vibreoffice.N001" oor:op="replace">
<prop oor:name="Context" oor:type="xs:string">
<value>com.sun.star.text.TextDocument,com.sun.star.drawing.DrawingDocument,com.sun.star.presentation.PresentationDocument</value>
</prop>
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en">vibreoffice - Toggle Vi Mode</value>
</prop>
<prop oor:name="URL" oor:type="xs:string">
<value>vnd.sun.star.script:vibreoffice.vibreoffice.Main?language=Basic&amp;location=application</value>
</prop>
<prop oor:name="Target" oor:type="xs:string">
<value>_self</value>
</prop>
</node>
</node>
</node>
</oor:component-data>

View File

@ -0,0 +1,2 @@
Vi Mode Extension for LibreOffice/OpenOffice

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">
<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
<manifest:file-entry manifest:media-type="application/vnd.sun.star.help"
manifest:full-path="help" />
<manifest:file-entry manifest:media-type="application/vnd.sun.star.basic-library"
manifest:full-path="vibreoffice/" />
<manifest:file-entry manifest:media-type="application/vnd.sun.star.configuration-data"
manifest:full-path="AddonUI.xcu" />
</manifest:manifest>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Extension files were created by Extension Compiler - version 2.1.1 -->
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:dep="http://openoffice.org/extensions/description/2006"
xmlns:xlink="http://www.w3.org/1999/xlink" >
<identifier value="vibreoffice" />
<version value="%VIBREOFFICE_VERSION%" />
<extension-description>
<src xlink:href="Descriptions/descr-en.txt" lang="en"/>
</extension-description>
<dependencies>
<OpenOffice.org-minimal-version value="3.1" dep:name="OpenOffice.org 3.1 minimum, or more recent" />
</dependencies>
<display-name>
<name lang="en">Vibreoffice - Vi-Mode Extension</name>
</display-name>
<publisher>
<name lang="en" xlink:href="http://www.seanyeh.com">Sean Yeh</name>
</publisher>
<release-notes>
<src lang="en" xlink:href="https://github.com/seanyeh/vibreoffice"/>
</release-notes>
</description>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<helpdocument version="1.0">
<meta>
<topic id="vibreoffice.Pagexx">
<title id="tit" xml-lang="en">Vibreoffice - Vi-Mode Extension : Write the title here</title>
<filename>/vibreoffice/Pagexx.xhp</filename>
</topic>
</meta>
<body>
<paragraph role="heading" level="1" xml-lang="en" id="N0001">
Write your own help pages from here
</paragraph>
<paragraph role="paragraph" xml-lang="en" id="N0002">
Language en is the default value, you should have an english version of your help if your extension is internationally available.
</paragraph>
<paragraph role="paragraph" xml-lang="en" id="N0003">
</paragraph>
<paragraph role="paragraph" xml-lang="en" id="N0004">
</paragraph>
<paragraph role="paragraph" xml-lang="en" id="N0005">
</paragraph>
<paragraph role="paragraph" xml-lang="en" id="N0006">
</paragraph>
<paragraph role="paragraph" xml-lang="en" id="N0007">
</paragraph>
<paragraph role="paragraph" xml-lang="en" id="N0008">
</paragraph>
</body>
</helpdocument>

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd">
<library:library xmlns:library="http://openoffice.org/2000/library" library:name="vibreoffice" library:readonly="false" library:passwordprotected="false"/>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd">
<library:library xmlns:library="http://openoffice.org/2000/library" library:name="vibreoffice" library:readonly="false" library:passwordprotected="false">
<library:element library:name="vibreoffice"/>
</library:library>

View File

@ -0,0 +1,422 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="vibreoffice" script:language="StarBasic">
REM ***** BASIC *****
Option Explicit
&apos; --------
&apos; Globals
&apos; --------
global VIBREOFFICE_STARTED as boolean &apos; Defaults to False
global VIBREOFFICE_ENABLED as boolean &apos; Defaults to False
global oXKeyHandler as object
&apos; Global State
global MODE as string
global VIEW_CURSOR as object
global MULTIPLIER as integer
global isRunning as boolean
&apos; -----------
&apos; Singletons
&apos; -----------
Function getCursor
getCursor = VIEW_CURSOR
End Function
Function getTextCursor
dim oTextCursor
oTextCursor = getCursor().getText.createTextCursorByRange(getCursor())
&apos; oTextCursor.gotoRange(oTextCursor.getStart(), False)
getTextCursor = oTextCursor
End Function
&apos; -----------------
&apos; Helper Functions
&apos; -----------------
Sub restoreStatus &apos;restore original statusbar
dim oLayout
oLayout = thisComponent.getCurrentController.getFrame.LayoutManager
oLayout.destroyElement(&quot;private:resource/statusbar/statusbar&quot;)
oLayout.createElement(&quot;private:resource/statusbar/statusbar&quot;)
End Sub
Sub setRawStatus(rawText)
thisComponent.Currentcontroller.StatusIndicator.Start(rawText, 0)
End Sub
Sub setStatus(statusText)
setRawStatus(MODE &amp; &quot; | &quot; &amp; statusText)
End Sub
Sub setMode(modeName)
MODE = modeName
setRawStatus(modeName)
End Sub
Function gotoMode(sMode)
Select Case sMode
Case &quot;NORMAL&quot;:
setMode(&quot;NORMAL&quot;)
Case &quot;INSERT&quot;:
setMode(&quot;INSERT&quot;)
Case &quot;VISUAL&quot;:
setMode(&quot;VISUAL&quot;)
dim oTextCursor
oTextCursor = getTextCursor()
&apos; Deselect TextCursor
oTextCursor.gotoRange(oTextCursor.getStart(), False)
&apos; Show TextCursor selection
thisComponent.getCurrentController.Select(oTextCursor)
End Select
End Function
&apos; --------------------
&apos; Multiplier functions
&apos; --------------------
Sub _setMultiplier(n as integer)
MULTIPLIER = n
End Sub
Sub resetMultiplier()
_setMultiplier(0)
End Sub
Sub addToMultiplier(n as integer)
dim sMultiplierStr as String
dim iMultiplierInt as integer
&apos; Max multiplier: 10000 (stop accepting additions after 1000)
If MULTIPLIER &lt;= 1000 then
sMultiplierStr = CStr(MULTIPLIER) &amp; CStr(n)
_setMultiplier(CInt(sMultiplierStr))
End If
End Sub
&apos; Should only be used if you need the raw value
Function getRawMultiplier()
getRawMultiplier = MULTIPLIER
End Function
&apos; Same as getRawMultiplier, but defaults to 1 if it is unset (0)
Function getMultiplier()
If MULTIPLIER = 0 Then
getMultiplier = 1
Else
getMultiplier = MULTIPLIER
End If
End Function
&apos; -------------
&apos; Key Handling
&apos; -------------
sub sStartXKeyHandler
sStopXKeyHandler()
oXKeyHandler = CreateUnoListener(&quot;KeyHandler_&quot;, &quot;com.sun.star.awt.XKeyHandler&quot;)
ThisComponent.CurrentController.AddKeyHandler(oXKeyHandler)
end sub
sub sStopXKeyHandler
ThisComponent.CurrentController.removeKeyHandler(oXKeyHandler)
&apos;oXKeyHandler = Nothing &apos;To know later this handler has stopped.
end sub
sub XKeyHandler_Disposing(oEvent)
end sub
&apos; --------------------
&apos; Main Key Processing
&apos; --------------------
function KeyHandler_KeyPressed(oEvent) as boolean
&apos; Exit if plugin is not enabled
If IsMissing(VIBREOFFICE_ENABLED) Or Not VIBREOFFICE_ENABLED Then
KeyHandler_KeyPressed = False
Exit Function
End If
dim bConsumeInput, bIsMultiplier, bIsModified, oTextCursor
bConsumeInput = True &apos; Block all inputs by default
bIsMultiplier = False &apos; reset multiplier by default
bIsModified = oEvent.Modifiers &gt; 1 &apos; If Ctrl or Alt is held down. (Shift=1)
&apos; MsgBox(oEvent.KeyCode &amp; &quot;,&quot; &amp; oEvent.KeyChar &amp; &quot;,&quot; &amp; oEvent.KeyFunc &amp; &quot;,&quot; &amp; oEvent.Modifiers)
&apos; --------------------------
&apos; Process global shortcuts, exit if matched (like ESC)
If ProcessGlobalKey(oEvent) Then
&apos; Pass
ElseIf MODE = &quot;INSERT&quot; Then
bConsumeInput = False &apos; Allow all inputs
&apos; If Change Mode
ElseIf ProcessModeKey(oEvent) Then
REM do nothing
ElseIf ProcessNumberKey(oEvent) Then
bIsMultiplier = True
&apos; Normal Key
ElseIf Not ProcessNormalKey(oEvent) and bIsModified Then
&apos; If is modified but doesn&apos;t match a normal command, allow input
&apos; (Useful for built-in shortcuts like Ctrl+s, Ctrl+w)
bConsumeInput = False
End If
&apos; --------------------------
&apos; Reset multiplier
If not bIsMultiplier Then resetMultiplier()
setStatus(getMultiplier())
&apos; Show terminal-like cursor
oTextCursor = getTextCursor()
If MODE = &quot;NORMAL&quot; Then
oTextCursor.gotoRange(oTextCursor.getStart(), False)
oTextCursor.goRight(1, False)
oTextCursor.goLeft(1, True)
thisComponent.getCurrentController.Select(oTextCursor)
ElseIf MODE = &quot;INSERT&quot; Then
oTextCursor.gotoRange(oTextCursor.getStart(), False)
thisComponent.getCurrentController.Select(oTextCursor)
End If
KeyHandler_KeyPressed = bConsumeInput
End Function
Function KeyHandler_KeyReleased(oEvent) As boolean
KeyHandler_KeyReleased = (MODE = &quot;NORMAL&quot;) &apos;cancel KeyReleased
End Function
&apos; ----------------
&apos; Processing Keys
&apos; ----------------
Function ProcessGlobalKey(oEvent)
dim bMatched
bMatched = True
Select Case oEvent.KeyCode
&apos; PRESSED ESCAPE
Case 1281:
&apos; Move cursor back if was in INSERT (but stay on same line)
If MODE &lt;&gt; &quot;NORMAL&quot; And Not getCursor().isAtStartOfLine() Then
getCursor().goLeft(1, False)
End If
gotoMode(&quot;NORMAL&quot;)
Case Else:
bMatched = False
End Select
ProcessGlobalKey = bMatched
End Function
Function ProcessNumberKey(oEvent)
dim c
c = CStr(oEvent.KeyChar)
If c &gt;= &quot;0&quot; and c &lt;= &quot;9&quot; Then
addToMultiplier(CInt(c))
ProcessNumberKey = True
Else
ProcessNumberKey = False
End If
End Function
Function ProcessModeKey(oEvent)
dim bMatched
bMatched = True
Select Case oEvent.KeyChar
&apos; Insert modes
Case &quot;i&quot;, &quot;a&quot;, &quot;I&quot;, &quot;A&quot;:
If oEvent.KeyChar = &quot;a&quot; Then getCursor().goRight(1, False)
If oEvent.KeyChar = &quot;I&quot; Then ProcessMovementKey(&quot;^&quot;)
If oEvent.KeyChar = &quot;A&quot; Then ProcessMovementKey(&quot;$&quot;)
gotoMode(&quot;INSERT&quot;)
Case &quot;v&quot;:
gotoMode(&quot;VISUAL&quot;)
Case Else:
bMatched = False
End Select
ProcessModeKey = bMatched
End Function
Function ProcessNormalKey(oEvent)
dim i, bMatched, bIsVisual
bMatched = False
bIsVisual = (MODE = &quot;VISUAL&quot;) &apos; is this hardcoding bad? what about visual block?
For i = 1 To getMultiplier()
dim bMatchedMovement, bMatchedDelete
bMatchedMovement = ProcessMovementKey(oEvent.KeyChar, bIsVisual, oEvent.Modifiers)
bMatchedDelete = ProcessDeleteKey(oEvent)
bMatched = bMatched or bMatchedMovement or bMatchedDelete
&apos; Special case: Break from For loop if in visual mode and has deleted,
&apos; since multiplier should not be applied
If bIsVisual and bMatchedDelete Then Exit For
Next i
ProcessNormalKey = bMatched
End Function
Function ProcessDeleteKey(oEvent)
dim oTextCursor, bMatched
oTextCursor = getTextCursor()
bMatched = True
Select Case oEvent.KeyChar
&apos; Case &quot;d&quot;:
&apos; setSpecial(&quot;d&quot;)
Case &quot;x&quot;:
&apos; oTextCursor.goRight(1, True)
thisComponent.getCurrentController.Select(oTextCursor)
&apos;
oTextCursor.setString(&quot;&quot;)
Case Else:
bMatched = False
End Select
ProcessDeleteKey = bMatched
End Function
&apos; -----------------------
&apos; Main Movement Function
&apos; -----------------------
&apos; Default: bExpand = False, keyModifiers = 0
Function ProcessMovementKey(keyChar, Optional bExpand, Optional keyModifiers)
dim oTextCursor, bSetCursor, bMatched
oTextCursor = getTextCursor()
bMatched = True
If IsMissing(bExpand) Then bExpand = False
If IsMissing(keyModifiers) Then keyModifiers = 0
&apos; Check for modified keys (Ctrl, Alt, not Shift)
If keyModifiers &gt; 1 Then
dim isControl
isControl = (keyModifiers = 2) or (keyModifiers = 8)
&apos; Ctrl+d and Ctrl+u
If isControl and keyChar = &quot;d&quot; Then
getCursor().ScreenDown(bExpand)
ElseIf isControl and keyChar = &quot;u&quot; Then
getCursor().ScreenUp(bExpand)
Else
bMatched = False
End If
ProcessMovementKey = bMatched
Exit Function
End If
&apos; Set global cursor to oTextCursor&apos;s new position if moved
bSetCursor = True
Select Case keyChar
Case &quot;l&quot;:
oTextCursor.goRight(1, bExpand)
Case &quot;h&quot;:
oTextCursor.goLeft(1, bExpand)
&apos; BEGIN HACK
&apos; oTextCursor.goUp and oTextCursor.goDown SHOULD work, but doesn&apos;t (I dont know why).
&apos; So this is a weird hack
Case &quot;k&quot;:
&apos;oTextCursor.goUp(1, False)
getCursor().goUp(1, bExpand)
bSetCursor = False
Case &quot;j&quot;:
&apos;oTextCursor.goDown(1, False)
getCursor().goDown(1, bExpand)
bSetCursor = False
&apos; END HACK
Case &quot;^&quot;:
getCursor().gotoStartOfLine(bExpand)
bSetCursor = False
Case &quot;$&quot;:
dim oldPos, newPos
oldPos = getCursor().getPosition()
getCursor().gotoEndOfLine(bExpand)
newPos = getCursor().getPosition()
&apos; If the result is at the start of the line, then it must have
&apos; jumped down a line; goLeft to return to the previous line.
&apos; Except for: Empty lines (check for oldPos = newPos)
If getCursor().isAtStartOfLine() And oldPos.Y() &lt;&gt; newPos.Y() Then
getCursor().goLeft(1, bExpand)
End If
&apos; maybe eventually cursorGoto... should return True/False for bsetCursor
bSetCursor = False
Case &quot;w&quot;, &quot;W&quot;:
oTextCursor.gotoNextWord(bExpand)
Case &quot;b&quot;, &quot;B&quot;:
oTextCursor.gotoPreviousWord(bExpand)
Case &quot;e&quot;:
oTextCursor.gotoEndOfWord(bExpand)
Case &quot;)&quot;:
oTextCursor.gotoNextSentence(bExpand)
Case &quot;(&quot;:
oTextCursor.gotoPreviousSentence(bExpand)
Case &quot;}&quot;:
oTextCursor.gotoNextParagraph(bExpand)
Case &quot;{&quot;:
oTextCursor.gotoPreviousParagraph(bExpand)
Case Else:
bSetCursor = False
bMatched = False
End Select
&apos; If oTextCursor was moved, set global cursor to its position
If bSetCursor Then
getCursor().gotoRange(oTextCursor.getStart(), False)
End If
&apos; If oTextCursor was moved and is in VISUAL mode, update selection
if bSetCursor and bExpand then
thisComponent.getCurrentController.Select(oTextCursor)
end if
ProcessMovementKey = bMatched
End Function
Sub initVibreoffice
&apos; Initializing
VIEW_CURSOR = thisComponent.getCurrentController.getViewCursor
resetMultiplier()
gotoMode(&quot;NORMAL&quot;)
sStartXKeyHandler()
End Sub
Sub Main
If Not VIBREOFFICE_STARTED Then
initVibreoffice()
VIBREOFFICE_STARTED = True
End If
&apos; Toggle enable/disable
VIBREOFFICE_ENABLED = Not VIBREOFFICE_ENABLED
End Sub
</script:module>