Monday, August 10, 2009

Building a VI clone with Groovy

The great thing about online spaces is they tend to be able to build themselves. Give enough decent tools for the users of a space, and they can make some kickass things. And bizarre things too. And very very garish things :(

Anyway, MUDS also allow this sort of thing, but in a more controlled fashion. MUDS generally have some sort of hierarchy to prevent the idiots from changing bits of the game that are really cool and ruining it for others. Called "creators", these hand picked super users can change parts of the game to make it more exciting, add depth and new content as often as required.

So I need some tools for the creators in my MUD to go about editing the groovy scripts and other files. Which brings me to the problem: there are no telnet based text editors written in either Java or Groovy that can do this. Guess I'll have to build my own? GroovyMud already has a command loop type shell implementation, but adding an entire shell for this sort of thing is a little overkill. With GroovyMud I am able to run scripts as commands (remember last time?) in the MUD by just typing the name, and the CommandInterpreter invokes the GroovyScriptEngine to run it. I can leverage this behaviour nicely :D

I will be logging my progress in creating this editor here when I can, so keep tuned to find out how I get on.

So to start, I struck on having a vi type editor for the interface. For those of you not familiar with vi, (get out now!! :D just kidding), it is a very simple *nix text editor with a lot of very cool functions. There are no menus, just a number of commands that can put you into different states in order to do stuff with the text.

For example, start up vi and push "i", and you enter "input" mode. Enter characters to add text to the file, and then push escape. You return back to movement mode, where you can move around the text file using the arrow keys. In movement mode, you can not only move the caret around, but delete characters with 'x' and lines by pressing 'd' and 'd' again. The default mode is movement, so you can move to where ever you need to add stuff in your file.

So an enum for these states would be useful, with our default:


enum Mode {
INSERT, APPEND, MOVE
}
Mode currentMode = Mode.MOVE


Obviously we would also need to store the position of our caret in the text file, so Groovy's StringBuffer and file manipulation api extensions should help me here:


int xPos = 0
int yPos = 0

StringBuffer fileContents = new StringBuffer(new File(argstr).getText())


In GroovyMud land, the player that is performing the edit is passed into the script as a paramter called "source". They have a very nice handy telnet ExtendedTerminalIO object to play with, which for these purposes acts just like any other kind of stream (except I can read lines with it with readln). So I can get a handle on this to manipulate their IOStream to their telnet prompt like this:


ExtendedTerminalIO stream = source.terminalOutput
stream.eraseToBeginOfScreen() // nifty function to clear the screen
stream.write(fileContents) // write out the file contents
stream.moveCursor(xPos, yPos) // move the cursor to the beginning of the file.


This is good, I now have a screen full of file with my cursor at the start. But how would I work within the framework of the script to provide the command interface? Then I hit on the idea of using a map to hold the actions for each key press:


def commandMap = ["i": {currentMode = INSERT},
"a" : {currentMode = APPEND},
"x" : { ... perform some delete function ...},

]


Ok so far so good. We also need some kind of input loop, to trap the users input until we exit. So setting up a while loop here would probably be a good thing:


boolean exit = false

while(!exit){
char ch = stream.read()
//perform the command in the map first
}


Ok this is as far as I have got. In theory I should be able to call the command in the map with the character that I read in. The closures there will handle the movement of the caret, the deletion of the chars in the string and any other functions, depending on the MODE I am in. I also want to add more funky stuff too, like code completion, colourization and other stuff like that.

More next time.

2 comments:

  1. Why not just invoke vim?

    Charles Nutter had some success with FFI

    http://blog.headius.com/2009/05/fork-and-exec-on-jvm-jruby-to-rescue.html

    If you are serious about building a VI clone you should look at jcurses. I have a less clone for log files that builds on jcurses

    http://yuri.baulsupp.com/2008/06/kolja-log-tools.html

    But I would advise against that option :)

    ReplyDelete
  2. Excellent blog you’ve got here.. It’s difficult to find high-quality writing like yours nowadays. I really appreciate individuals like you! Take care!! You can visit my site.
    changeparts

    ReplyDelete