From 918ea95a487217c37b1379c727aa27378b6860c3 Mon Sep 17 00:00:00 2001 From: Sean Yeh Date: Mon, 22 Dec 2014 17:41:53 -0600 Subject: [PATCH] Version 0.1 - readme, install files, and more --- Makefile | 22 + README.md | 56 ++- compile.sh | 22 + dist/vibreoffice-0.1.0.oxt | Bin 0 -> 10686 bytes extension/template/AddonUI.xcu | 25 ++ extension/template/Descriptions/descr-en.txt | 2 + extension/template/META-INF/manifest.xml | 10 + extension/template/description.xml | 23 + .../template/help/en/vibreoffice/Pagexx.xhp | 35 ++ extension/template/vibreoffice/dialog.xlb | 3 + extension/template/vibreoffice/script.xlb | 5 + .../template/vibreoffice/vibreoffice.xba | 422 ++++++++++++++++++ 12 files changed, 621 insertions(+), 4 deletions(-) create mode 100644 Makefile create mode 100755 compile.sh create mode 100644 dist/vibreoffice-0.1.0.oxt create mode 100644 extension/template/AddonUI.xcu create mode 100644 extension/template/Descriptions/descr-en.txt create mode 100644 extension/template/META-INF/manifest.xml create mode 100644 extension/template/description.xml create mode 100644 extension/template/help/en/vibreoffice/Pagexx.xhp create mode 100644 extension/template/vibreoffice/dialog.xlb create mode 100644 extension/template/vibreoffice/script.xlb create mode 100644 extension/template/vibreoffice/vibreoffice.xba diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c62ec65 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 174639d..acda449 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..ba6aa03 --- /dev/null +++ b/compile.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# +# "Compile" BASIC macro file to LibreOffice-compatible xba file + +# compile SRC DESTINATION +compile() { + # Escape XML &<>'" + src=`sed "s/\&/\&/g; s//\>/g; s/'/\'/g; s/\"/\"/g" "$1"` + + xbafile="$2" + name="`basename -s \".xba\" "$xbafile"`" + + XBA_TEMPLATE='\n\n\n%s\n' + + printf "$XBA_TEMPLATE" "$name" "$src" > "$xbafile" +} + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 SOURCE DESTINATION" +else + compile "$1" "$2" +fi diff --git a/dist/vibreoffice-0.1.0.oxt b/dist/vibreoffice-0.1.0.oxt new file mode 100644 index 0000000000000000000000000000000000000000..d4542b12146cf3f1bda6c83486359380c88191ef GIT binary patch literal 10686 zcmbW71yo#HviBQz_uy{9-QC^Y-3i(R3lbo>1_z5T@5$La1=zk!hLy`EwK>z?vPyhhqXM~-Ry_LC{vkN`Y z&h~e>(wGYSE+XWh8(s*rU}tW%m^7*g6>4Q2i}K(|@bU;zfRSyFgTelwT%KfdC%AiC z>w|o5%8*u$(7n7?+&Z}&V{DL4mTcU5krycguu4By!J~qCO8YypLAvCDOEgg$=^6^RXIy-nBXnBl7+2ao~ ztxmj9qQLP4HCnn)tQ}D=sdALI z@=}Vm`bS9rJ}dCAA$7Adb~1A?H@7k|`!5pHPdl#H`kE%v*W_USg<@)DWb0r-541J@ zU1szmW#Bp)kpoYjL+jm}rsG0t40VK%53&e&d(DeKvg^d&-rqo$Ao0?54g0WM+^(>1 zNcYZ^kYYm-Lw_eP*3KZA!z!LY-{>*qm%Yg4JY=4jk{}&1nK7tXWZ*auZHHz$k^G^i zmWW=?MVv22qtCs4{yo;M^8%%c4tnNVG(q(54sJ5P1J$VJ%^mSQ0ywidn=oDZf$H&Gb;;;@s9_-V0mR`XK<1(Mw z#Ad!>TKSNETjMPU`wyW`{_}{*^K^?vs=fw8rBQa?%^}L`BnYVv4Zhk@PK&)SA5(}t zxA@d^-%6&>A(|s`=MIsCpeAu(mLoG-Cyi�jhcRu!4y;$1QsNc@?^-0OhuM`IJ=1 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 Ye+Ptsd|gcdh_A2i*Q&Hd_Q$9H0m-Rsc>n+a literal 0 HcmV?d00001 diff --git a/extension/template/AddonUI.xcu b/extension/template/AddonUI.xcu new file mode 100644 index 0000000..36155a0 --- /dev/null +++ b/extension/template/AddonUI.xcu @@ -0,0 +1,25 @@ + + + + + + + + com.sun.star.text.TextDocument,com.sun.star.drawing.DrawingDocument,com.sun.star.presentation.PresentationDocument + + + vibreoffice - Toggle Vi Mode + + + vnd.sun.star.script:vibreoffice.vibreoffice.Main?language=Basic&location=application + + + _self + + + + + + diff --git a/extension/template/Descriptions/descr-en.txt b/extension/template/Descriptions/descr-en.txt new file mode 100644 index 0000000..5496090 --- /dev/null +++ b/extension/template/Descriptions/descr-en.txt @@ -0,0 +1,2 @@ +Vi Mode Extension for LibreOffice/OpenOffice + diff --git a/extension/template/META-INF/manifest.xml b/extension/template/META-INF/manifest.xml new file mode 100644 index 0000000..7b1bdc4 --- /dev/null +++ b/extension/template/META-INF/manifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/extension/template/description.xml b/extension/template/description.xml new file mode 100644 index 0000000..9354c0b --- /dev/null +++ b/extension/template/description.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + Vibreoffice - Vi-Mode Extension + + + Sean Yeh + + + + + diff --git a/extension/template/help/en/vibreoffice/Pagexx.xhp b/extension/template/help/en/vibreoffice/Pagexx.xhp new file mode 100644 index 0000000..f80e91c --- /dev/null +++ b/extension/template/help/en/vibreoffice/Pagexx.xhp @@ -0,0 +1,35 @@ + + + + + Vibreoffice - Vi-Mode Extension : Write the title here + /vibreoffice/Pagexx.xhp + + + + + Write your own help pages from here + + + Language en is the default value, you should have an english version of your help if your extension is internationally available. + + + + + + + + + + + + + + + + + + + + + diff --git a/extension/template/vibreoffice/dialog.xlb b/extension/template/vibreoffice/dialog.xlb new file mode 100644 index 0000000..806f969 --- /dev/null +++ b/extension/template/vibreoffice/dialog.xlb @@ -0,0 +1,3 @@ + + + diff --git a/extension/template/vibreoffice/script.xlb b/extension/template/vibreoffice/script.xlb new file mode 100644 index 0000000..df7b26b --- /dev/null +++ b/extension/template/vibreoffice/script.xlb @@ -0,0 +1,5 @@ + + + + + diff --git a/extension/template/vibreoffice/vibreoffice.xba b/extension/template/vibreoffice/vibreoffice.xba new file mode 100644 index 0000000..0f8ec68 --- /dev/null +++ b/extension/template/vibreoffice/vibreoffice.xba @@ -0,0 +1,422 @@ + + + +REM ***** BASIC ***** +Option Explicit + +' -------- +' Globals +' -------- +global VIBREOFFICE_STARTED as boolean ' Defaults to False +global VIBREOFFICE_ENABLED as boolean ' Defaults to False + +global oXKeyHandler as object + +' Global State +global MODE as string +global VIEW_CURSOR as object +global MULTIPLIER as integer + +global isRunning as boolean + +' ----------- +' Singletons +' ----------- +Function getCursor + getCursor = VIEW_CURSOR +End Function + +Function getTextCursor + dim oTextCursor + oTextCursor = getCursor().getText.createTextCursorByRange(getCursor()) + ' oTextCursor.gotoRange(oTextCursor.getStart(), False) + + getTextCursor = oTextCursor +End Function + +' ----------------- +' Helper Functions +' ----------------- +Sub restoreStatus 'restore original statusbar + dim oLayout + oLayout = thisComponent.getCurrentController.getFrame.LayoutManager + oLayout.destroyElement("private:resource/statusbar/statusbar") + oLayout.createElement("private:resource/statusbar/statusbar") +End Sub + +Sub setRawStatus(rawText) + thisComponent.Currentcontroller.StatusIndicator.Start(rawText, 0) +End Sub + +Sub setStatus(statusText) + setRawStatus(MODE & " | " & statusText) +End Sub + +Sub setMode(modeName) + MODE = modeName + setRawStatus(modeName) +End Sub + +Function gotoMode(sMode) + Select Case sMode + Case "NORMAL": + setMode("NORMAL") + Case "INSERT": + setMode("INSERT") + Case "VISUAL": + setMode("VISUAL") + + dim oTextCursor + oTextCursor = getTextCursor() + ' Deselect TextCursor + oTextCursor.gotoRange(oTextCursor.getStart(), False) + ' Show TextCursor selection + thisComponent.getCurrentController.Select(oTextCursor) + End Select +End Function + + +' -------------------- +' Multiplier functions +' -------------------- +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 + + ' Max multiplier: 10000 (stop accepting additions after 1000) + If MULTIPLIER <= 1000 then + sMultiplierStr = CStr(MULTIPLIER) & CStr(n) + _setMultiplier(CInt(sMultiplierStr)) + End If +End Sub + +' Should only be used if you need the raw value +Function getRawMultiplier() + getRawMultiplier = MULTIPLIER +End Function + +' 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 + + +' ------------- +' Key Handling +' ------------- +sub sStartXKeyHandler + sStopXKeyHandler() + + oXKeyHandler = CreateUnoListener("KeyHandler_", "com.sun.star.awt.XKeyHandler") + ThisComponent.CurrentController.AddKeyHandler(oXKeyHandler) +end sub + +sub sStopXKeyHandler + ThisComponent.CurrentController.removeKeyHandler(oXKeyHandler) + + 'oXKeyHandler = Nothing 'To know later this handler has stopped. +end sub + +sub XKeyHandler_Disposing(oEvent) +end sub + + +' -------------------- +' Main Key Processing +' -------------------- +function KeyHandler_KeyPressed(oEvent) as boolean + ' 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 ' Block all inputs by default + bIsMultiplier = False ' reset multiplier by default + bIsModified = oEvent.Modifiers > 1 ' If Ctrl or Alt is held down. (Shift=1) + + ' MsgBox(oEvent.KeyCode & "," & oEvent.KeyChar & "," & oEvent.KeyFunc & "," & oEvent.Modifiers) + + ' -------------------------- + ' Process global shortcuts, exit if matched (like ESC) + If ProcessGlobalKey(oEvent) Then + ' Pass + + ElseIf MODE = "INSERT" Then + bConsumeInput = False ' Allow all inputs + + ' If Change Mode + ElseIf ProcessModeKey(oEvent) Then + REM do nothing + + ElseIf ProcessNumberKey(oEvent) Then + bIsMultiplier = True + + ' Normal Key + ElseIf Not ProcessNormalKey(oEvent) and bIsModified Then + ' If is modified but doesn't match a normal command, allow input + ' (Useful for built-in shortcuts like Ctrl+s, Ctrl+w) + bConsumeInput = False + End If + ' -------------------------- + + + ' Reset multiplier + If not bIsMultiplier Then resetMultiplier() + setStatus(getMultiplier()) + + ' Show terminal-like cursor + oTextCursor = getTextCursor() + If MODE = "NORMAL" Then + oTextCursor.gotoRange(oTextCursor.getStart(), False) + oTextCursor.goRight(1, False) + oTextCursor.goLeft(1, True) + thisComponent.getCurrentController.Select(oTextCursor) + + ElseIf MODE = "INSERT" 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 = "NORMAL") 'cancel KeyReleased +End Function + + +' ---------------- +' Processing Keys +' ---------------- +Function ProcessGlobalKey(oEvent) + dim bMatched + bMatched = True + Select Case oEvent.KeyCode + ' PRESSED ESCAPE + Case 1281: + ' Move cursor back if was in INSERT (but stay on same line) + If MODE <> "NORMAL" And Not getCursor().isAtStartOfLine() Then + getCursor().goLeft(1, False) + End If + + gotoMode("NORMAL") + Case Else: + bMatched = False + End Select + ProcessGlobalKey = bMatched +End Function + + +Function ProcessNumberKey(oEvent) + dim c + c = CStr(oEvent.KeyChar) + + If c >= "0" and c <= "9" Then + addToMultiplier(CInt(c)) + ProcessNumberKey = True + Else + ProcessNumberKey = False + End If +End Function + + +Function ProcessModeKey(oEvent) + dim bMatched + bMatched = True + Select Case oEvent.KeyChar + ' Insert modes + Case "i", "a", "I", "A": + If oEvent.KeyChar = "a" Then getCursor().goRight(1, False) + If oEvent.KeyChar = "I" Then ProcessMovementKey("^") + If oEvent.KeyChar = "A" Then ProcessMovementKey("$") + + gotoMode("INSERT") + Case "v": + gotoMode("VISUAL") + Case Else: + bMatched = False + End Select + ProcessModeKey = bMatched +End Function + + +Function ProcessNormalKey(oEvent) + dim i, bMatched, bIsVisual + bMatched = False + bIsVisual = (MODE = "VISUAL") ' 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 + + ' Special case: Break from For loop if in visual mode and has deleted, + ' 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 + ' Case "d": + ' setSpecial("d") + + Case "x": + ' oTextCursor.goRight(1, True) + thisComponent.getCurrentController.Select(oTextCursor) + ' + oTextCursor.setString("") + Case Else: + bMatched = False + + End Select + + ProcessDeleteKey = bMatched +End Function + + +' ----------------------- +' Main Movement Function +' ----------------------- +' 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 + + + ' Check for modified keys (Ctrl, Alt, not Shift) + If keyModifiers > 1 Then + dim isControl + isControl = (keyModifiers = 2) or (keyModifiers = 8) + + ' Ctrl+d and Ctrl+u + If isControl and keyChar = "d" Then + getCursor().ScreenDown(bExpand) + ElseIf isControl and keyChar = "u" Then + getCursor().ScreenUp(bExpand) + Else + bMatched = False + End If + + ProcessMovementKey = bMatched + Exit Function + End If + + ' Set global cursor to oTextCursor's new position if moved + bSetCursor = True + + Select Case keyChar + Case "l": + oTextCursor.goRight(1, bExpand) + Case "h": + oTextCursor.goLeft(1, bExpand) + + ' BEGIN HACK + ' oTextCursor.goUp and oTextCursor.goDown SHOULD work, but doesn't (I dont know why). + ' So this is a weird hack + Case "k": + 'oTextCursor.goUp(1, False) + getCursor().goUp(1, bExpand) + bSetCursor = False + Case "j": + 'oTextCursor.goDown(1, False) + getCursor().goDown(1, bExpand) + bSetCursor = False + ' END HACK + + Case "^": + getCursor().gotoStartOfLine(bExpand) + bSetCursor = False + Case "$": + dim oldPos, newPos + oldPos = getCursor().getPosition() + getCursor().gotoEndOfLine(bExpand) + newPos = getCursor().getPosition() + + ' If the result is at the start of the line, then it must have + ' jumped down a line; goLeft to return to the previous line. + ' Except for: Empty lines (check for oldPos = newPos) + If getCursor().isAtStartOfLine() And oldPos.Y() <> newPos.Y() Then + getCursor().goLeft(1, bExpand) + End If + + ' maybe eventually cursorGoto... should return True/False for bsetCursor + bSetCursor = False + + Case "w", "W": + oTextCursor.gotoNextWord(bExpand) + Case "b", "B": + oTextCursor.gotoPreviousWord(bExpand) + Case "e": + oTextCursor.gotoEndOfWord(bExpand) + + Case ")": + oTextCursor.gotoNextSentence(bExpand) + Case "(": + oTextCursor.gotoPreviousSentence(bExpand) + Case "}": + oTextCursor.gotoNextParagraph(bExpand) + Case "{": + oTextCursor.gotoPreviousParagraph(bExpand) + Case Else: + bSetCursor = False + bMatched = False + End Select + + ' If oTextCursor was moved, set global cursor to its position + If bSetCursor Then + getCursor().gotoRange(oTextCursor.getStart(), False) + End If + + ' 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 + ' Initializing + VIEW_CURSOR = thisComponent.getCurrentController.getViewCursor + resetMultiplier() + + gotoMode("NORMAL") + + sStartXKeyHandler() +End Sub + +Sub Main + If Not VIBREOFFICE_STARTED Then + initVibreoffice() + VIBREOFFICE_STARTED = True + End If + + ' Toggle enable/disable + VIBREOFFICE_ENABLED = Not VIBREOFFICE_ENABLED +End Sub + \ No newline at end of file