Ansillary.jl

Ansillary.AnsillaryModule

Ansillary is a package for interacting with ANSI compatible terminals.

Ansillary aims to support only the commonly supported capabilities of ANSI terminals, i.e. completely replacing ncurses is not a goal of Ansillary. Explicitly, if a capability is not available in one of the following terminals then it will not be supported in Ansillary:

  • LibVTE terminals.
  • LibVTerm terminals.
  • The Linux console.
  • Konsole.
  • XTerm.

The reason for this is simple, reliable capability detection is harder than writing a package that wraps ncurses (or similar libraries such as termbox). Do note, however, that if a terminal does not support a capability but the lack of that capability does not cause issues for the user then it will be supported. For example, bracketed paste mode is supported despite the Linux console not actually having a paste feature because the Linux console still parses and accepts the required escape sequences even though they have no effect. Whereas scrolling is not supported because the Linux console does not support scrolling and not having something scroll is something that the user will definitely notice.

The package is currently split into three modules: Cursor for controlling the position and visibilty of the cursor, Inputs for reading input events from a terminal, and Screen for controlling what is displayed.

Ansillary.CursorModule

This module deals with controlling the cursor.

The two major pieces of functionality in this module are moving the cursor and hiding the cursor.

Move the cursor using the move! function:

move!(Up(3))

There are several different movements available, see documentation on the subtypes of Movement for more details.

It is possible to temporarily move the cursor to a different location using save or checkpoint:

move!(Coordinate(1, 1))
println("First line!")

save() do
	move!(Down(4))
	println("Fifth line!")

	checkpoint() do
		move!(Down(4))
		println("Tenth line!")

		checkpoint() do
			move!(Down(4))
			println("Fifteenth line!")
		end
	end
end

println("Second line!")

checkpoint() do
	move!(Down(4))
	println("Sixth line!")

	checkpoint() do
		move!(Down(4))
		println("Eleventh line!")
	end
end

The location of the cursor can also be found using location, though note that this only works in raw mode:

julia> Screen.raw(Cursor.location)
Ansillary.Inputs.Location(0x003c, 0x0001)

Hiding the cursor is done using Julia's support for do-notation:

julia> Cursor.hide() do
		   for c in "There is no cursor..."
			   print(c)
			   sleep(0.1)
		   end
	   end
There is no cursor...
Ansillary.Cursor.RowType

Move the cursor to a given row without changing it's column.

Warning
This movement currently does not work properly with `Inputs.EventLoop` due to it's use of `Cursor.location`.
Ansillary.Cursor.checkpointFunction

A nestable implementation of save.

Warning
This function will not work correctly with [`Inputs.EventLoop`](@ref).
Warning
This function will only work correctly when using [`Screen.raw`](@ref).

This function will save the current location of the cursor, run the function, then move the cursor back to it's original location.

Ansillary.Cursor.locationFunction

Get the current location of the cursor.

Warning
This function will only work properly in raw mode, e.g. `Screen.raw(Cursor.location)`.
Ansillary.Cursor.saveFunction

Return the cursor to it's current location after the function has finished.

Tip
Use this function instead of [`checkpoint`](@ref) if you are not using raw mode or if you are using [`Inputs.EventLoop`](@ref).
Warning
This uses the ANSI code for saving the cursor so it can't be nested, use [`checkpoint`](@ref) if these calls need to be nested.
Ansillary.InputsModule

This module deals with reading events from the terminal.

Mostly these events will be input from the user, but some of the functionality is about the terminal sending messages as well.

The fundamental piece of functionality that this module offers is being able to a single event from an input stream.

julia> read(IOBuffer("[7;2~"), Event)
Shift+Home

If Ansillary does not recognise an event it will return the bytes that it has read and any remaining bytes in the stream.

julia> read(IOBuffer("[7;2~"), Event)
Ansillary.Inputs.Unknown(UInt8[0x1b, 0x5b, 0x51, 0x1b, 0x5b, 0x37, 0x3b, 0x32, 0x7e])

This is done to minimise the risk of the application receiving a seemingly valid event that is actually part of an unknown event, which could lead to highly undesirable consequences. Note that this is not completely foolproof, for example Konsole sends "@sy" when Super+y is pressed and Ansillary will happily accept that as four separate events.

julia> let input = IOBuffer("@sy"); [read(input, Event), read(input, Event), read(input, Event), read(input, Event)] end
4-element Array{Ansillary.Inputs.Key,1}:
 Ctrl+x
 @
 s
 y

Events is an iterator that wraps read(::Any, ::Event), see the file examples/keylogger.jl to see how that works. EventLoop is an iterator that also sends a Tick event every period, see the file examples/snake.jl or tests/interactive.jl to see how that works (also make sure to pay attention to the warnings on that type's documentation).

This module also contains the paste function, which allows you to turn on bracketed paste mode. In bracketed paste mode when the user pastes something into the terminal, a PasteStart event will be sent, followed by the pasted text, then finally a PasteEnd event.

Warning
The functionality of this module will only work properly if the terminal is in raw mode, e.g.

```julia
Screen.raw() do
	for key in Events(stdin)
		@show key
		if key == CTRL_C
			break
		end
	end
end
```
Ansillary.Inputs.EventLoopType

Iterate over a simple event loop.

The event loop will send a Tick event every period (though this isn't guaranteed, for example computationally heavy loop bodies will cause the tick to be sent after the body finishes), and send any received input events between those ticks.

Warning
There is currently a bug in this iterator which makes it "eat" the next byte sent to the stream. This shouldn't be an issue in the common use-case of an event loop, where the process exits when the event loop does.
Warning
The current implementation is incompatible with the following methods because there is a background task constantly reading from the input stream:

* [`Cursor.location`](@ref).
* [`Cursor.move!`](@ref) methods that take a [`Cursor.Row`](@ref).
* [`Screen.size`](@ref).

Examples

for key in Inputs.EventLoop(Inputs.Second(1))
	@show key
	if key == CTRL_C
		break
	end
end
Ansillary.Inputs.EventsType

Iterate over input events.

Examples

for key in Inputs.Events(stdin)
	@show key
	if key == Inputs.CTRL_C
		break
	end
end
Ansillary.Inputs.ModifiedType

A key event that has been modified, e.g. Ctrl+c.

A Modified event can be created using the + operator:

julia> Ctrl()+'c' == Modified(Character('c'), [Ctrl()])
true

julia> Ctrl()+Super()+Insert() == Modified(Insert(), [Super(), Ctrl()])
true

The order of modifiers given to the constructor is not important:

julia> Modified(Delete(), [Ctrl(), Alt()]) == Modified(Delete(), [Alt(), Ctrl()])
true
Ansillary.ScreenModule

This module deals with controlling what is displayed on the screen.

The two major features of this module are switching to the alernative screen and clearing the what is currenlty on the screen.

The alternative screen is a feature of most terminals that allows an application to switch to a screen that has no scrollback, allowing it to draw whatever it wants to the terminal without interfering with the scrollback of the normal screen. This is most useful for full-screen applications, such as vim or emacs. Ansillary allows you to enter the alternative screen with the alternative function:

julia> Screen.alternative() do
		   println("No scrollback!")
		   read(stdin, UInt8)
	   end

Ansillary will set raw mode (also known as non-canonical mode) when alternative is called as this is almost always what is wanted, to avoid this use the non-exported alternative! and standard! functions.

It also possible to only set raw mode using the raw function. The main benefits of raw mode are that the input stream is not line buffered allowing one byte to be read at a time (which in turn allows the application to respond to key presses), and that the input is not printed directly to the output allowing the application to handle printable input in it's own way (so that it can implement vim-style keybindings, for example).

To clear the screen Ansillary provides the clear! function, as well as the Area types for specifying which parts of the screen need clearing.

This short script

print("Some line...")
move!(Left(3))
clear!(FromCursorForward())
print("!")

will result in Some line! being printed to the terminal.

This module also provides the size as a slightly nicer wrapper around displaysize.

Ansillary.Screen.alternativeFunction

Temporarily activate the alernative screen for the duration of the function.

This function also sets the terminal to raw mode as it is rare that you'll need the alternative screen but not raw mode. If the alternative screen is needed without setting raw mode, use alternative! and standard! directly.

Ansillary.Screen.rawFunction

Temporarily set the terminal to raw mode for the duration of the function.

If switching to raw mode permanently is required use REPL.Terminals.raw!.