diff --git a/site/source/blog/2017-06-30-howl-0-5-released.md b/site/source/blog/2017-06-30-howl-0-5-released.md new file mode 100644 index 000000000..3120db084 --- /dev/null +++ b/site/source/blog/2017-06-30-howl-0-5-released.md @@ -0,0 +1,155 @@ +--- +title: Howl 0.5 released! +location: Stockholm, Sweden +--- + +We are very happy to announce the release of [Howl](http://howl.io/) 0.5! +Highlights of this release are below and the full changelog since 0.4 is +included at the bottom of this blog post. + +READMORE + +### Code inspection support + +Code inspections integrates various types of annotations, typically from linters +and similar checkers, directly into Howl: + +![Buffer inspections](/images/screenshots/monokai/buffer-inspect.png) + +The 0.5 release ships with built-in inspection support for + +- Lua (using [luacheck](https://github.com/mpeterv/luacheck)) +- Moonscript (using [moonpick](https://github.com/nilnor/moonpick)) +- Ruby (using the interpreter's built-in syntax checking) + +Support for more languages will likely come in the future. It's easy to add +support for your own custom inspectors as well using the new inspect API. + +### Revamped configuration system + +Howl has always had a pretty flexible configuration system, allowing you to set +configuration for different layers: globally, for a specific mode or a specific +buffer. With 0.5 this is cranked up a notch, as it's now possible to set +configuration not only for the aforementioned layers, but for different type of +scopes as well. Having scopes allows you to specify configuration for a +particular file for instance, or all files below a particular directory. Or +files using a particular mode below a particular directory.. It makes for a very +powerful and flexible configuration system, and is something that future +releases is likely to build upon, e.g. to introduce project specific +configuration, etc. + +You can read more about the new configuration system in the +[manual](/doc/manual/configuration.html#configuration-variables). + +### New bundles + +- A new [Rust](http://www.rust-lang.org) bundle was added with lexing and +indentation support. + +- A new Cython bundle provides syntax and structure support for +[Cython](http://cython.org) code. + +- A new Dart bundle provides syntax and structure support for +[Dart](https://www.dartlang.org) code. + +![New bundles](/images/blog/0-5-released/new-bundles.png) + +### New and improved commands + +- Added new commands `editor-move-text-left` and `editor-move-text-right`, bound +to `alt_left` and `alt_right` by default. These move the current character or +selected text left or right by one character while preserving the selection. + +- Added new commands `editor-move-lines-up` and `editor-move-lines-down`, bound +to `alt_up` and `alt_down` by default. These move the current or selected lines +up (or down) by one line while preserving the selection. + +- Added new command, `editor-replace-exec`, for replacing selection or buffer +content with the result of piping it to an external command. + +## Full Changelog since 0.4 + +### New and improved + +- New Dart bundle for [Dart](https://www.dartlang.org) code. + +- Make fixes to let OpenBSD build cleanly (thanks @oficial) + +- Various improvements for VI mode + +- Code inspection support for Lua using +[luacheck](https://github.com/mpeterv/luacheck) + +- Code inspection support for Ruby using Ruby interpreter + +- Code inspection support for Moonscript using +[moonpick](https://github.com/nilnor/moonpick) + +- Support for a new inspections framework (i.e. linting). + +- New Rust bundle provides syntax and structure support for +[Rust](http://www.rust-lang.org) code. + +- Added `--version` command line flag. + +- Bundles can now declare dependencies on other modules using the +`require_bundle` helper function. + +- Bundles can now expose modules using `provide_module` helper function. + +- LuaJIT was updated to LuaJIT-2.1.0-beta3 + +- Theme compatibility fixes for newer Gtk versions + +- Quiet Gtk size allocation warnings in newer Gtk versions + +- Added support for X11 primary selection (e.g. copy & paste using middle +button). + +- New Cython bundle provides syntax and structure support for +[Cython](http://cython.org) code. + +- **breaking** - Default for `line-padding` setting has been changed to `0`. If +you've relied on it: set it explicitly to its' previous value `1` in your Howl +configuration. + +- **breaking** - Overhauled the configuration system to use a flexible *scope* +and *layer* structure. Replaced all 'set*' commands with a new `set` command as +part of this. See the documentation for more details. + +- Added `config.save_config_on_exit` variable to automatically save global +configuration to `~/.howl/system/config.lua`. + +- Added the `save-config` command that saves the current global configuration to +`~/.howl/system/config.lua`. + +- Changed undo coalescing to not be as greedy (e.g. coalescing pastes and +ordinary edit revisions). + +- Added `custom_draw` flair type (`highlight.CUSTOM`). + +- Added command line help which is invoked by pressing `f1` while any +interactive command is running. This displays a popup containing information +about the command. + +- Added new commands `editor-move-text-left` and `editor-move-text-right`, bound +to `alt_left` and `alt_right` by default. These move the current character or +selected text left or right by one character while preserving the selection. + +- Added new commands `editor-move-lines-up` and `editor-move-lines-down`, bound +to `alt_up` and `alt_down` by default. These move the current or selected lines +up (or down) by one line while preserving the selection. + +- Bundled all required dependencies for running specs: `./bin/howl-spec` can now +be run without any type of external dependecy. + +- Upgrade Moonscript to 0.5.0 + +- Added new command, `editor-replace-exec`, for replacing selection or buffer +content with the result of piping it to an external command. + +### Bugs fixed + +- Issues as seen on +[Github](https://github.com/howl-editor/howl/issues?utf8=✓&q=closed%3A2016-05-30..2017-06-05%20is%3Aissue%20is%3Aclosed +sort%3Acreated-asc) diff --git a/site/source/doc/index.haml b/site/source/doc/index.haml index ffb461d3e..5253520cb 100644 --- a/site/source/doc/index.haml +++ b/site/source/doc/index.haml @@ -7,12 +7,12 @@ title: Howl Documentation (master branch) %p This is the documentation for the master branch, documentation for older releases can be found here: - %a.alert-link(href = "/versions/0.4/doc/") [Version 0.4] - , - %a.alert-link(href = "/versions/0.3/doc/") [Version 0.3] + %a.alert-link(href = "/versions/0.5/doc/") [Version 0.5] , + %a.alert-link(href = "/versions/0.4/doc/") [Version 0.4] + \. - Documentation is ever a ongoing work, and additional documentation + Documentation is ever ongoing work, and additional documentation will appear here as it is written. However, don't hesitate to %a.alert-link(href = "/contact.html") get in contact if you have any questions. diff --git a/site/source/getit.md b/site/source/getit.md index 846d8fb61..ecdeb2501 100644 --- a/site/source/getit.md +++ b/site/source/getit.md @@ -5,26 +5,26 @@ title: Installation # Installing Howl Howl is developed on Linux, but it builds on other \*NIX platforms as well such -as FreeBSD (with other \*BSDs presumably requiring only little work). It should -be possible to port to OSX or Windows, should any brave soul be willing to put in -the work. +as FreeBSD and OpenBSD (with other \*BSDs presumably requiring only little +work). It should be possible to port to OSX or Windows, should any brave soul be +willing to put in the work. You can install Howl by building it from source, either from a release or by cloning the repository from Github. ## Latest release -The latest release of Howl is 0.4.1. It was released at 2016-10-14, and is +The latest release of Howl is 0.5. It was released at 2017-06-30, and is available for download from: -[https://github.com/howl-editor/howl/releases/download/0.4.1/howl-0.4.1.tgz](https://github.com/howl-editor/howl/releases/download/0.4.1/howl-0.4.1.tgz) +[https://github.com/howl-editor/howl/releases/download/0.5/howl-0.5.tgz](https://github.com/howl-editor/howl/releases/download/0.5/howl-0.5.tgz) -_MD5_: 9ef463f4d8b31e8954e70e507fbb1858 +_MD5_: 3bd902adb1fa8116053431f81b9a8b1f -_SHA1_: 1434af03c5bc9f10d64ef93ca0ae68ccc7092fee +_SHA1_: 905179ceb78f80ffd2612c5f22067d7b7b336bbc __Release notes:__ -[Howl 0.4.1 Released](/blog/2016/10/14/howl-0-4-1-released.html) +[Howl 0.5 Released](/blog/2017/06/30/howl-0-5-released.html) ## Building Howl from source @@ -101,6 +101,14 @@ and confusion to arise. ## Older releases +### Howl 0.4.1 released 2016-10-14 + +_MD5_: 9ef463f4d8b31e8954e70e507fbb1858 + +_SHA1_: 1434af03c5bc9f10d64ef93ca0ae68ccc7092fee + +[Download](https://github.com/howl-editor/howl/releases/download/0.4.1/howl-0.4.1.tgz) + ### Howl 0.4, released 2016-05-31 [Download](https://github.com/howl-editor/howl/releases/download/0.4/howl-0.4.tgz) diff --git a/site/source/images/blog/0-5-released/new-bundles.png b/site/source/images/blog/0-5-released/new-bundles.png new file mode 100644 index 000000000..11ea86f0d Binary files /dev/null and b/site/source/images/blog/0-5-released/new-bundles.png differ diff --git a/site/source/index.haml b/site/source/index.haml index bc79dd0bd..0e3af8599 100644 --- a/site/source/index.haml +++ b/site/source/index.haml @@ -48,12 +48,12 @@ .col-lg-4 %h2 Try it out! %p - Howl #{link_to "version 0.4.1", "/getit.html"} - was released on 2016-10-14. Howl source is available on + Howl #{link_to "version 0.5", "/getit.html"} + was released on 2016-06-30. Howl source is available on #{link_to "GitHub", "https://github.com/howl-editor/howl"} and released under the #{link_to "MIT", "http://opensource.org/licenses/MIT"} license. - Howl is currently developed on and available for Linux, though porting to other - operating systems is possible. + Howl is developed on Linux and should build cleanly at least on Linux, + FreeBSD and OpenBSD. Porting to other operating systems should be possible. %a.btn.btn-primary(href="getit.html") Download & install » diff --git a/site/source/layouts/base_layout.haml b/site/source/layouts/base_layout.haml index 68278cb6a..26aa95f8d 100644 --- a/site/source/layouts/base_layout.haml +++ b/site/source/layouts/base_layout.haml @@ -59,7 +59,7 @@ .footer-blurb %div The Howl editor. %div - Copyright 2012-2016 + Copyright 2012-2017 %a.alert-link(href='https://github.com/howl-editor/howl/contributors') The Howl Developers. diff --git a/site/source/versions/0.3/doc/api/application.html b/site/source/versions/0.3/doc/api/application.html deleted file mode 100644 index 8351c0611..000000000 --- a/site/source/versions/0.3/doc/api/application.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - Howl :: howl.Application - - - - - - - -
-
- -
- -

howl.Application

-
- howl.Application -
- -
-

The Application object acts as the main hub within the Howl editor. There exists one and only one instantiated application object per Howl instance, available as howl.app.

Properties

buffers

A list of currently open Buffer:s. The list is ordered by how recently a buffer was shown. Thus, a currently showing buffer will come before a buffer that is not shown, and not showing buffers will be ordered according to the timestamp they were last shown.

editor

Points to the currently active Editor, if any.

editors

A list of all existing Editor:s. Each editor can be placed in only one window at a time, but this list holds all editors present for the current Howl instance - regardless of whether they’re placed in the currently focused window or not.

next_buffer

This is the most recent buffer that is currently not showing in any editor. If all buffers are currently showing it’s the first buffer in .buffers.

window

Points to the currently focused Window.

windows

A list of existing Window:s.

Methods

add_buffer (buffer, show = true)

Adds the existing buffer to .buffers. If show is true, shows the buffer in the currently active editor.

close_buffer (buffer, force = false)

Removes buffer from .buffers. If the buffer is modified, and force is not true, the user is prompted before closing the buffer.

editor_for_buffer (buffer)

Returns the editor currently showing buffer, or nil if the buffer is not currently showing in any editor.

new_buffer (buffer_mode = nil)

Creates a new buffer, and adds it to .buffers. buffer_mode can optionally be specified to assign a specific mode for the new buffer directly. When not specified, the default mode is used. See mode for more information about buffer modes.

new_editor (options = {})

Creates a new Editor. Unless options specify otherwise, the newly created editor is added to the currently focused window, to the right of the currently focused existing editor. It’s set to show the buffer from the .next_buffer property. The editor is added to .editors before the return of the method.

options can contain any of the following keys:

Example use (Moonscript):

buffer = howl.app\new_buffer!
buffer.text = 'Show this text in the new buffer'
howl.app\new_editor :buffer

new_window (properties = {})

Creates a new application Window. properties is table of window properties to set for the new window, such as title, height and width. The window is added to .windows before the return of the method. Returns the newly created window.

open_file (file, editor = _G.editor)

Opens the provided file. By default, unless editor specifies a specific editor to open the file into, the file is opened in the currently active editor. Emits the file-opened signal if the file was opened successfully. If the file was successfully opened, returns the Buffer and the Editor holding the buffer. Otherwise nil is returned.

save_all ()

Saves all modified buffers in one go.

save_session ()

Saves the current editing session to disk. This includes things such as information about what buffers are open, the current state of the window, etc.

synchronize ()

Synchronizes all open files with their respective files, if any. This will cause any non-modified buffers to be reloaded from disk, should the file be more recently modified than the buffer.

quit (force = false)

Requests for Howl to quit. If any open buffers are modified, and force is not true, the user will be prompted for verification before actually quitting.

- -
- - - diff --git a/site/source/versions/0.3/doc/api/buffer.html b/site/source/versions/0.3/doc/api/buffer.html deleted file mode 100644 index 75846830f..000000000 --- a/site/source/versions/0.3/doc/api/buffer.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - Howl :: howl.Buffer - - - - - - - -
-
- -
- -

howl.Buffer

-
- howl.Buffer -
- -
-

Overview

Buffers are in-memory containers of text. While they are often associated with a specific file on disk, this need not be the case. Instead they simply represent some textual content loaded into memory, available for manipulation within Howl. Buffer manipulation is typically done in one of two ways; it can be done as a result of a user interacting with an Editor displaying a particular buffer, or it can be done programmatically by manipulating the buffer directly.

Buffers can be created directly and used without being associated with a file or an Editor, or without showing up in the buffer list. But if you want the buffer to show up in the buffer list you need to either create it through, or register it with, Application.

See also:

Properties

can_undo

Whether the buffer contains any undo information that can be undo via the undo method. You can assign false to clear any undo information currently available for a particular buffer.

local buffer = Buffer()
buffer.text = 'my buffer text!'
print(buffer.can_undo)
-- => true
buffer.can_undo = false
print(buffer.can_undo)
-- => false

config

A configuration object that can be used to access and manipulate config variables for a certain buffer. This object is automatically chained to the buffer’s mode’s config property, meaning it will defer to what is set for the mode (and in extension set globally) should a particular configuration variable not be set specifically for the buffer.

data

A general-purpose table that can be used for storing arbitrary information about a particular buffer. Intended as a way for any Howl code to have a place to assign data with a buffer. Similar to properties but ephemeral, i.e. any data in this table will be lost upon a restart. As this is shared by all Howl code, take care to namespace any specific data properly.

eol

The line ending currently in effect for the buffer. One of:

file

An optional file associated with the current buffer. Assigning to this causes the buffer to be associated with assigned file, and loaded with the file’s contents. If the file does not exist, the buffer’s current contents will be emptied. The buffer’s title is automatically updated from the file’s name as part of the assignment.

last_shown

A timestamp value, as obtained from Lua’s os.time, specifying when the buffer was last showing.

length

The length of the buffer’s text, in code points.

lines

An instance of Lines for the buffer that allows for line based access to the content.

mode

The buffer’s mode. When assigning to this:

modified

A boolean indicating whether the buffer is modified or not. You can explicitly assign to this to force a particular status.

modified_on_disk

For a buffer with an associated file, this is a boolean indicating whether the file has changed since its contents was loaded into the buffer. Always false for a buffer without an associated file.

multibyte

A boolean indicating whether the buffer’s text contains multibyte characters.

properties

A general-purpose table that can be used for storing arbitrary information about a particular buffer. Intended as a way for any Howl code to have a place where to store persistent information for a buffer. The contents of this is automatically serialized and restored with the session. As this is shared by all Howl code, take care to namespace any specific data properly.

read_only

A boolean specifying whether the buffer is read-only or not. A read-only buffer can not be modified. Assign to this to control the status.

showing

A boolean indicating whether the buffer is currently showing in any editor.

size

The size of the buffer’s text, in bytes.

text

The buffer’s text. Assigning to this causes the entire buffer contents to be replaced with the assigned text.

title

The buffer’s title. This is automatically set whenever assigning a file to a buffer, but can be explicitly specified as well. Assigning to this causes the buffer-title-set signal to be emitted.

Functions

Buffer(mode = {})

Creates a new buffer, optionally specifying its mode.

Methods

append(text)

Appends text to the end of the buffer’s current text.

as_one_undo(f)

Invokes the function f, and collects any modifications performed within f as one undo group. Calling this, and subsequently calling undo will thus undo all modifications made within f.

byte_offset(char_offset)

Returns the byte offset corresponding to the passed char_offset. Raises an error if char_offset is out of bounds.

char_offset(byte_offset)

Returns the character offset corresponding to the passed byte_offset. Raises an error if byte_offset is out of bounds.

chunk(start_pos, end_pos)

Returns a Chunk for the given range.

context_at(pos)

Returns a Context for the specified position.

delete(start_pos, end_pos)

Deletes the text between start_pos and end_pos, which specify an inclusive range.

find(search, init = 1)

Searches for the text search in the the buffer’s text starting at character position init. Returns character offsets start_pos, end_pos of the first match, or nil if no match was found. A negative init specifies an offset from the end, where -1 means the last character of the buffer.

See also: rfind()

insert(text, pos)

Inserts text at the position given by pos, and returns the position right after newly inserted text. examples.

lex(end_pos)

Lexes the buffer content using the modes lexer, if available. The content is lexed up until end_pos.

redo()

Redo the last, previously undone, buffer modification.

reload (force = false)

Reloads the buffer contents from its associated file. Raises an error if the buffer does not have any associated file. Emits the buffer-reloaded signal. Returns true if the buffer was successfully loaded and false otherwise. A modified buffer will not be reloaded (with false being returned), unless force is true.

replace(pattern, replacement)

Replaces all occurrences of pattern with replacement, and returns the number of replacements made. pattern can be either a Lua pattern, or a regular expression.

rfind(search, init = @length)

Reverse search: searches backwards for the text search in the buffer’s text starting at the character position init. Returns character offsets start_pos, end_pos of the first match, or nil if no match was found. A negative init specifies an offset from the end, where -1 means the last character of the buffer. The rightmost character of the match found may be at the init position, however, no part of the match will be to the right of init.

See also: find()

save()

Saves the buffer’s content to its associated file, if any. Emits the buffer-saved signal. As part of saving the content, optionally removes any trailing white-space and ensures that there’s an eol at the end of the file, according to the strip_trailing_whitespace and ensure_newline_at_eof configuration variables.

save_as (file)

Associates the buffer with, and saves the buffer’s content to file. The save is performed using the same semantics as for save().

sub(start_pos, end_pos)

Returns the text from character offset start_pos to end_pos (both inclusive). Returns an empty string when start_pos is larger than end_pos. Negative offsets count from end of the buffer.

local buffer = Buffer('abcde')
print(buffer\sub(1, 2))
-- => 'ab'
print(buffer\sub(-2, -1))
-- => 'de'

undo()

Undo the last buffer modification.

- -
- - - diff --git a/site/source/versions/0.3/doc/api/clipboard.html b/site/source/versions/0.3/doc/api/clipboard.html deleted file mode 100644 index 17d2e6200..000000000 --- a/site/source/versions/0.3/doc/api/clipboard.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - Howl :: howl.clipboard - - - - - - - -
-
- -
- -

howl.clipboard

-
- howl.clipboard -
- -
-

Overview

The clipboard module keeps track of copied text in Howl, and handles synchronization with the system clipboard. It provides two ways of remembering clipboard items: As a list of anynomous clips that is automatically updated with each copy/delete/cut operation, and within named registers.

Clipboard items

A clipboard item is a simple Lua table. The simplest and most common type of item contains only one field, text, that contains the text of the item. There is no real restriction on what additional fields can be available for a clipboard item (the fields can be specified when doing a push), but so far one specific field is in use; The whole_lines field, when set to true, indicates that the text should be considered a block of stand-alone lines, rather than a simple chunk of text.


See also:

Properties

clips

A table (list) of clipboard items available on the clipboard, with the most recent item being at index 1 in the table. The maximum number of clipboard items is controlled by the clipboard_max_items config variable. clips is automatically updated whenever a new item is push()ed, prepending the new item and removing older items as necessary.

current

The most recent anynomous clipboard item available on the clipboard.

registers

A table containing named clipboard items. As an example, suppose a clipboard item containing the text “hello” has been push()ed to the abc register. In that case the registers table would look like the following:

{
  abc = {
    text: 'hello'
  }
}

Functions

push (item, options = {})

Pushes the specified item to the clipboard. item can either be table containing a text field, along with optional additional fields, or it can be a string in which case a clipboard item table is automatically constructed.

The optional options table allows for specifying a named register where to store the item. The to field specifies the register name to use for storing the clip in this case.

The pushed item is made available to the system clipboard automatically, except when pushing to a named register using the to field in options.

Examples:

-- Push some text to the clipboard
howl.clipboard.push('my text')

-- Push a clipboard item with additional fields
howl.clipboard.push({text = 'my text', whole_lines = true})

-- Push some text to the register 'a'
howl.clipboard.push('my text', { to = 'a' } )
- -
- - - diff --git a/site/source/versions/0.3/doc/api/config.html b/site/source/versions/0.3/doc/api/config.html deleted file mode 100644 index 2cfaa6ec5..000000000 --- a/site/source/versions/0.3/doc/api/config.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - Howl :: howl.config - - - - - - - -
-
- -
- -

howl.config

-
- howl.config -
- -
-

Overview

Things that are meant to be configurable in Howl are exposed as “configuration variables”. Configuration variables can be set either interactively from within Howl, using the set command, or programmatically from code. To get an overview of currently available variables, type set and press space at the readline to view a list.

Configuration variables can be specified at three different levels in Howl, in ascending order of priority:

The value set for the variable is used unless overridden by a mode or buffer specific setting (the set command always sets variables globally).

The value is set for a particular mode (e.g. “Lua” or “Ruby”), and is applied whenever a buffer with that particular mode is active. The value is used unless overridden by a buffer specific setting, and overrides any global setting.

The value is set for a particular buffer, and is applied whenever that buffer is active. The value overrides any mode specific or global setting.

As described above, variables can be set on three different levels. No matter the on what level they’re set, they’re always set (and accessed) using config objects. For global accesses, you would use howl.config (this module). For mode variables you access variables using the config object on a particular mode instance, and similarly for buffer variables you use the config object for a particular buffer.

The following code snippet illustrates the idiomatic ways of setting variables on different levels:

howl.config.my_var = 'foo'
howl.mode.by_name('ruby').config.my_var = 'foo'
howl.app:new_buffer().config.my_var = 'foo'

See also:

Properties

definitions

A table of all known variables definitions, keyed by the variable name. For more information about the structure of the definitions, see define.

Functions

define (options)

Defines a new config variable. Options can contain the following fields:

get (name)

Gets the global value of the variable named name. While getting the value of a variable using get is perfectly fine, note that the idiomatic way of getting variables values globally is to just to index the config module, like so:

local val = howl.config.my_variable

local_proxy ()

Returns a new configuration proxy object. A proxy object offers access to all configuration variables defined in Howl, using simple indexing:

proxy = howl.config.local_proxy()
proxy.indent -- => 2

Assigning to a proxy object only sets the value locally however:

proxy = howl.config.local_proxy()
proxy.indent = 5
howl.config.indent -- => 2
proxy.indent -- => 5

Proxy objects offers one additional feature in addition to the above; the possibility of chaining to a different configuration object other than the global howl.config module. Using the chain_to method, it’s possible to create hierarchies of configuration objects (as is done in Howl for modes and buffers):

proxy = howl.config.local_proxy()
next_proxy = howl.config.local_proxy()
next_proxy.chain_to(proxy)

In the above example, proxy would defer any lookups not set locally to the global howl.config module, and next_proxy would defer any lookups to proxy. Proxies work against the global configuration variable definitions, and respects any validations, conversions, etc., specified.

set (name, value)

Globally sets the value of the configuration variable with name name to be value. An error is raised for any of the following scenarios:

Upon a successful change, any listeners are notified. To remove any previously set value, pass nil as value. While setting a variable using set is perfectly fine, note that the idiomatic way of setting variables globally is to just assign to the variable name in the config module, like so:

howl.config.my_variable = true

watch (name, callback)

Registers a listener for the variable named name. callback, which must be callable, will be invoked whenever the specified variable has a new value set. callback will be invoked with three parameters:

name - The name of the parameter being set value - The new value of the parameter is_local - A boolean indicating whether the value was set locally or globally.

- -
- - - diff --git a/site/source/versions/0.3/doc/api/interact.html b/site/source/versions/0.3/doc/api/interact.html deleted file mode 100644 index f3bbcdf11..000000000 --- a/site/source/versions/0.3/doc/api/interact.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - Howl :: howl.interact - - - - - - - -
-
- -
- -

howl.interact

-
- howl.interact -
- -
-

Overview

The howl.interact module acts as the central registry of interactions in Howl and lets you register new interactions as well as invoke interactions. An interaction is a piece of functionality that is invoked as a function call and it retrieves some information from the user. Interactions use the command line, and optionally additional widgets, to get information from the user. For example, consider this call to the read_text interaction:

name = howl.interact.read_text prompt: 'Enter your name:'

Here the read_text interaction displays the given prompt in the command line and lets the user type some text. When the user presses enter, the command line is closed and the read_text function returns the text entered by user. If the user presses escape, the function returns nil.

Interactions are commonly used to read user input when implementing interactive commands. Howl includes a number of built-in interactions, such as select_file, which lets the user choose a file, and select, which lets the user choose an item from a list of options - see Built-in interactions below for details.

An interaction is implemented as simple table that provides the name of the interaction, a description, and either a handler or a factory field. Simple interactions that just customize other interactions can be implemented easily with just a handler function. More complex interactions that need greater control on the command line behavior are implemented as factory based interactions. Details for both handler and factory based interactions are in register below.

Interactions are run by the command line, which maintains a stack of running interactions. While one or more interactions are running, the command line API can be used to display prompts in the command line, read and update the command line text, as well as attach helper widgets above the command line (for example, a ListWidget may show a completion list).


See also:

Functions

register (def)

Registers a new interaction. Registered interactions are available as fields on the interact module itself, using the interaction name.

def is a table with the following fields:

The handler function implements the interaction and returns the result of the interaction. Here is an example definition of a handler:

  handler: -> howl.interact.select items: {'red', 'blue', 'green'}

The above handler displays a selection list containing three items and lets the user select one. Note that it reuses the select interaction. Handler functions are blocking - i.e. the function does not return until the result of the interaction is available.

The handler function may accept arguments. Any arguments passed when calling the interaction are passed through to the handler function.

Interactions can also be implemented as factory based interactions. The factory field is a function that returns an interaction instance table. This table describes how various events should be handled and has the following fields:

Here is an example implementation of an interaction using a factor:

  factory: -> {
    run: (@finish) =>
      @command_line = howl.app.window.command_line
      @command_line.prompt = 'Text:'

    on_update: (text) =>
      log.info text

    keymap:
      enter: => self.finish @command_line.text
      escape: => self.finish!
      binding_for:
        ['view-close']: => self.finish!
  }

The above example displays ‘Text:’ as the command line prompt and lets the user enter any text in the command line. Whenever the text is updated by the user, the interaction shows it in an info message. When the user presses enter, the interaction finishes, returning the text entered by the user. If the user presses escape, the interaction finishes, returning nil.

Note the special key called binding_for in the keymap. This demonstrates how a keystroke can be specified indirectly instead of by hard-coding. In the above example, the “view-close” key within “binding-for” refers to the keystroke currently bound to the “view-close” command. This means if the user presses the keystroke that is bound to the “view-close” command - which is alt_w by default - the associated function will be invoked, closing the command line and returning nil. If the user has changed the key binding for the “view-close” command, that keystroke will be bound to the function above instead.

unregister (name)

Unregister an interaction with the name name.

Built-in interactions

read_text (opts)

Lets the user enter free form text in the command line. Returns the text entered by the user when the user presses enter, or nil if the user presses escape. opts is a table that contains the following fields:

Example:

name = howl.interact.read_text title:'Name', prompt:'Enter name:'
log.info 'Hello '..name if name

select (opts)

Displays a list of options to the user and lets the user select one by using the up and down arrow keys and pressing enter. Also lets the user narrow down the options by typing something in the command line - the options list is then filtered to show only those items that match the entered text.

Allows customization such as multiple columns, column headers, styling, user provided selection etc. These are described below.

If the user presses enter, returns a table containing two fields - selection and text, where:

If the user presses escape, nil is returned.

opts is a table that specifies:

Examples:

The following example displays a list of three items with a column header. It also lets the user specify a color that is not in the given list.

color = howl.interact.select
  items: {'red', 'blue', 'green'}
  columns: {{header: 'Color'}}
  allow_new_value: true
if color
  if color.selection
    log.info 'You selected:'..color.selection
  else
    log.info 'You selected a new color:'..color.text

The following example displays a two column list. It also shows how string fields can be used in the items table. Unlike numerically indexed fields, string fields are not displayed, but they can be used to associate additional data with each item.

action = howl.interact.select
  items: {
    {'Run', 'Run this file', cmd: 'run'},
    {'Compile', 'Compile this file', cmd: 'compile'},
  }
if action
  if action.selection.cmd == 'run'
    log.info 'running...'
  else
    log.info 'compiling...'

select_buffer (opts)

Lets the user select a buffer from a list of all buffers. opts is a table containing:

Returns the Buffer selected by the user, or nil if the user presses escape.

select_directory (opts)

Lets the user select a directory. Displays sub directories in a completion list and allows the user to navigate the file system using either the completion list or typing a path in the command line.

opts is a table containing:

Returns the File selected by the user, or nil if the user presses escape. Note that if allow_new was specified, the returned file object may refer to a nonexistent path.

select_file (opts)

Lets the user select a file. Displays files in the completion list and allows the user to navigate the file system using the completion list or typing a path in the command line.

opts is a table containing:

Returns the File selected by the user, or nil if the user presses escape. Note that if allow_new was specified, the returned file object may refer to a nonexistent path.

select_file_in_project (opts)

Lets the user select a file from a completion list containing all files in the current project. opts is a table containing:

Returns the File selected by the user, or nil if the user presses escape.

select_line(opts)

Lets the user select a line from a list of source lines. opts is a table similar to the table accepted by select, with the following differences:

If the user presses enter, returns a table containing:

If the user presses escape, nil is returned.

select_location(opts)

Very similar to select, lets the user select an item from a list of options. In addition, it displays a preview of the currently selected option in the editor. Each item in items (or returned by matcher) must also have the following fields:

yes_or_no (opts)

Lets the user select either ‘Yes’ or ‘No’ as an answer to a question. Returns true if the user selects ‘Yes’, false if the user selects ‘No’ and nil if the user presses escape. opts is table containing:

- -
- - - diff --git a/site/source/versions/0.3/doc/api/regex.html b/site/source/versions/0.3/doc/api/regex.html deleted file mode 100644 index 5a93ddf4e..000000000 --- a/site/source/versions/0.3/doc/api/regex.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - Howl :: howl.regex - - - - - - - -
-
- -
- -

howl.regex

-
- howl.regex -
- -
-

Overview

Lua, by itself, does not provide regular expression. Instead it provides its own more limited form of pattern matching. Regular expressions are instead provided by the howl.regex module as an extension. To blend in with Lua, the operations provided by the regex module closely mimics the corresponding operations found in the Lua string standard library. Support for regular expressions is also included in Howl’s string extensions, making it easy to use within your code. Since regular expressions are not native to Lua, there’s no syntactical sugar available for constructing a regular expression. Instead regular expression are constructed as ordinary strings. The global function r provides a constructor function for this. Since this is available in the global namespace, it’s possible to construct a regular expression anywhere within Howl just by prefixing a string with r, like so:

my_regex = r'\\d+[lL]'

You can then either use provided methods directly on the regular expression:

r'(r\\w+)\\s+(\\S+)':match('red right hand') -- => "red", "right"

Or use Howl’s string extensions which allows for passing in regular expressions instead of Lua patterns:

local s = 'red right hand'
s:ufind(r'(\\w+)', 5) -- => 5, 9, "right"
s:umatch(r'(r\\w+)\\s+(\\S+)') -- => "red", "right"

Howl’s regular expressions are implemented as Perl compatible regular expressions. While it’s an implementation detail, susceptible to change, they are currently implemented on top of GLibs regular expression support. You can read more about the full syntax supported by the implementation here.

See also:

Properties

pattern

Holds the regular expression string used to construct the regular expression.

r('\\d+').pattern -- => '\\d+'

capture_count

Holds the the number of capturing groups in the regular expression:

r('foo(bar)(\\w+)').capture_count -- => 2

Functions

is_instance (v)

Returns true if v is a regular expression instance, and false otherwise.

r (pattern)

Constructs a regular expression from pattern. As was noted in the overview, this is available globally as simple r. Raises an error if pattern is not a valid regular expression. This function also accepts regular expressions as parameters, in which case the passed regular expression is returned as is.

Methods

match (string [, init])

Matches the regular expression against string. If init is specified, starts matching from that position. Has the same semantics as Lua’s string.match, with the one significant difference that init as well as any returned positional captures are treated as character offsets.

r'(r\\w+)\\s+(\\S+)':match('red right hand') -- => "red", "right"
r'()red()':match('red') -- => 1, 4
r'\\pL':find('!äö') -- => 2, 2

find (s [, init])

Finds the first match of the regular expression in s, optionally starting at init if specified. Has the same semantics as Lua’s string.find, with the one significant difference that init as well as any returned indices are treated as character offsets.

local s = 'red right hand'
s:ufind(r'(\\w+)', 5) -- => 5, 9, "right"

gmatch (s)

Returns an iterator function, where each consecutive call returns the next match of the regular expression in s. Has the same semantics as Lua’s string.gmatch, with the one significant difference that any positional captures are returned as character offsets.

[m for m in r'\\w+'\gmatch 'well hello there'] -- => { 'well', 'hello', 'there' }
[p for p in r'()\\s+'\gmatch 'ΘΙΚΛ ΞΟΠ ' ] -- => { 5, 9 }
- -
- - - diff --git a/site/source/versions/0.3/doc/api/signal.html b/site/source/versions/0.3/doc/api/signal.html deleted file mode 100644 index 1d118d741..000000000 --- a/site/source/versions/0.3/doc/api/signal.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - Howl :: howl.signal - - - - - - - -
-
- -
- -

howl.signal

-
- howl.signal -
- -
-

Overview

Signals provide a way of sending and receiving notifications about various events that happens within Howl. For example, there are signals emitted whenever text is added or deleted in a buffer, or a key is pressed in Howl, etc. By “connecting” a handler for signal, you can easily hook into the ordinary workings to add your own additional functionality. Signals are defined by their name, and each signal can provide additional information about the event as parameters. Each signal can have multiple handlers connected at a given time, which will all be invoked, provided a handler does not explicitly halt the processing (see emit for more information).

To view the list of currently registered signals within Howl as well as information about the parameters you can use the describe-signal command.

See also:

Properties

.all

This is a table of all currently defined signals within Howl, keyed by their name. The value associated with each key is the signal information as passed to register.

Functions

connect (name, handler [, index])

Connects handler to the signal specified by name. The optional index argument specifies where in the handler list the handler should be placed. All handlers for a specific signal are stored in a list, and the index specifies the order in which they are invoked whenever a signal is emitted, where the handler with index 1 is invoked first, followed by the handlers with greater indices.

An error is raised when trying to connect a handler for a signal that has not been registered.

disconnect (name, handler)

Disconnects handler from the signal specified by name.

emit (name, parameters)

Emits the signal specified by name, along with any optional parameters contained in parameters. parameters, if specified, should be a table with keys matching those of the parameters specified for register. An error is raised when trying to emit a signal that has not been registered.

When a signal is emitted each connected handler is invoked in turn, with parameters as the sole argument. Should any handler return true, the processing is halted and emit returns true. Otherwise, false is returned. Any error triggered in a signal handler is logged, and processing continues.

register (name, options)

Registers the signal specified in name, with the options specified in options. options can contain the following fields:

Example of how to register a signal:

signal.register 'mode-registered',
  description: 'Signaled right after a mode was registered',
  parameters:
    name: 'The name of the mode'

unregister (name)

Unregisters the signal specified by name.

- -
- - - diff --git a/site/source/versions/0.3/doc/api/timer.html b/site/source/versions/0.3/doc/api/timer.html deleted file mode 100644 index a8aff35a8..000000000 --- a/site/source/versions/0.3/doc/api/timer.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - Howl :: howl.timer - - - - - - - -
-
- -
- -

howl.timer

-
- howl.timer -
- -
-

Overview

The timer module provides support for “timers”, that is having functions invoked at a later time. All callbacks are always invoked on the main GUI thread.

See also:

Functions

asap (callback, …)

Invokes callback as soon as possible, passing along any optional extra parameters passed to asap. It might be unclear what the value of having a callback invoked as soon as possible is, compared to just invoking directly. The rationale for this is that there are cases where you want to schedule destructive buffer modifications, but are not allowed to do so at the current point in time (e.g. when in a signal handler for the text-deleted signal).

after (seconds, callback, …)

Invokes callback after approximately seconds seconds, passing along any optional extra parameters passed to after. seconds can contain fractions, allowing you schedule callbacks at sub-second rates.

As an example, the below snippet would cause the text “I was invoked with Log me!” to be logged after approximately 500 milliseconds:

callback = (text) ->
  log.info "I was invoked with #{text}"

timer.after 0.5, callback, 'Log me!'
- -
- - - diff --git a/site/source/versions/0.3/doc/api/ui/command_line.html b/site/source/versions/0.3/doc/api/ui/command_line.html deleted file mode 100644 index 8a263c11a..000000000 --- a/site/source/versions/0.3/doc/api/ui/command_line.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - Howl :: howl.ui.CommandLine - - - - - - - -
-
- -
- -

howl.ui.CommandLine

-
- howl.ui.CommandLine -
- -
-

Instances of CommandLine are used to control the command line widget to obtain user input while running an interaction. Each Window instance has an associated .command_line field which is used to access the command line instance.

Interactions work closely with the command line to obtain user input - the command line API is used from within a running interaction to update things like the displayed prompt. When no interaction is running the command line functionality is not available.

Using the command line API is essential only when implementing new interactions. Various built-in interactions can be used to obtain user input and should be preferred where applicable.

The command line maintains a stack of running interactions. Whenever an interaction is started, the new interaction is pushed onto the stack of running interactions. When an interaction finishes, it is popped off the stack. The command line maintains state for each running interaction independently. The topmost interaction on the stack is called the active interaction.

The command line view contains the following widgets:


See also:

Properties

prompt

The prompt shown in the command line. This property gets or sets the prompt for the active interaction. Read/write.

text

The user editable text currently in the command line. This property gets or sets the text for the active interaction. Read/write.

title

The title of the CommandLine view. This property only gets or sets the title for the active interaction. Read/write.

Methods

add_widget (name, widget)

Adds a custom widget widget in the command line view. name is the name used to identify the widget. Widgets added are associated with the active interaction and when the active interaction finishes, the associated widgets are automatically removed. Currently two types of widgets are available - widgets must be an instance of either ListWidget or NotificationWidget. Widgets may provide their own keymap. The keymap for the active interaction takes precedence over keymaps for the attached widgets.

clear ()

Clears the text part of the command line. The prompt is left intact.

clear_all ()

Clears the entire command line, including any prompts and texts from other running interactions. When the active interaction exits, the prompts and texts from other running interactions are automatically restored.

clear_notification ()

Clears any notification messages displayed, if any, and hides the notification bar.

notify (text, level=‘info’)

Shows the notification bar containing the text message. The level can be ‘info’, ‘warn’ or ‘error’ and the message is styled accordingly.

pop_spillover ()

Returns and clears any unprocessed spillover text. See write_spillover for a description of spillover.

remove_widget (name)

Removes the widget identified by name from the command line view.

write (text)

Appends text to the current text in the command line. This affects the text for the active interaction only.

write_spillover (text)

Saves text as the current spillover. The spillover is the part of the text that is left unprocessed by the current interaction but may be processed by another interaction that is invoked. There is only one spillover for the command line.

For example, if the text ‘open path/to/folder’ is pasted into the command line, the active interaction may only process ‘open’ and write ‘path/to/folder’ to spillover before invoking other interactions. An invoked interaction might then use pop_spillover to retrieve ‘path/to/folder’ and process it.

- -
- - - diff --git a/site/source/versions/0.3/doc/api/ui/editor.html b/site/source/versions/0.3/doc/api/ui/editor.html deleted file mode 100644 index ef3fef59e..000000000 --- a/site/source/versions/0.3/doc/api/ui/editor.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - - - - Howl :: howl.ui.Editor - - - - - - - -
-
- -
- -

howl.ui.Editor

-
- howl.ui.Editor -
- -
-

Editors are the primary way of manipulating Buffers. They’re graphical editing components which display the contents of a buffer visually and lets the user manipulate them. An editor always contains a buffer, and is typically always shown to the user.

See also:

Properties

active_chunk

Contains a Chunk representing the currently active text block. If no selection is present, the chunk contains the entire buffer. With a selection present, the chunk spans the current selection.

active_lines

Contains a list of the currently active lines. If no selection is present, this will contain one element, the current line. With a selection present, this holds all Lines included in the current selection.

buffer

Contains the current Buffer.

Assigning another buffer to this property would cause that buffer to be displayed in the editor, and would cause the before-buffer-switch and after-buffer-switch signals to be emitted.

cursor

A Cursor instance for the particular editor. Can be used to access and manipulate the cursor.

cursor_line_highlighted

A boolean controlling whether the line containing the cursor is highlighted.

Note that this is typically controlled via the cursor_line_highlighted configuration variable instead of being set explicitly for an editor instance.

current_context

Contains the currently active context, i.e. the context for the current cursor position. Read-only.

current_line

Contains the currently active line, i.e. the line that the cursor is currently positioned on. Read-only.

horizontal_scrollbar

A boolean controlling whether the editor shows a horizontal scrollbar or not.

Note that this is typically controlled via the horizontal_scrollbar configuration variable instead of being set explicitly for an editor instance.

indentation_guides

Controls how indentation guides are shown for the particular editor. Valid values are (strings):

Note that this is typically controlled via the indentation_guides configuration variable instead of being set explicitly for an editor instance.

indicator

A table of “indicators” for the current editor. An error is raised if you try to access an unknown indicator (see register_indicator for more information).

Example of modifying an existing indicator from a key handler:

howl.bindings.push {
  editor: {
    shift_i: (editor) ->
      editor.indicator.vi.label = 'My interesting VI info text'
  }
}

line_at_bottom

Holds the line number of the line visible at the bottom of the editor window. Assigning this scrolls the editor window so the specified line is visible as close to the bottom as possible.

line_at_center

Holds the line number of the line at the center of the editor window. Assigning this scrolls the editor window so the specified line is as close to the center as possible.

line_at_top

Holds the line number of the line visible at the top of the editor window. Assigning this scrolls the editor window so the specified line is visible as close to the top as possible.

line_numbers

A boolean controlling whether the editor shows line number to the left of the text or not.

Note that this is typically controlled via the line_numbers configuration variable instead of being set explicitly for an editor instance.

line_wrapping

Controls how line wrapping is performed. Valid values are (strings):

Note that this is typically controlled via the line_wrapping configuration variable instead of being set explicitly for an editor instance.

lines_on_screen

Holds the number of lines currently visible on the screen. Read-only.

overtype

A boolean indicating whether typing inserts new characters in the .buffer or overwrites them.

searcher

A Searcher instance for the particular editor. Can be used to initialize and manipulate searches for the containing editor.

selection

A Selection instance for the particular editor. Can be used to access and manipulate the selection.

vertical_scrollbar

A boolean controlling whether the editor shows a vertical scrollbar or not.

Note that this is typically controlled via the vertical_scrollbar configuration variable instead of being set explicitly for an editor instance.

Functions

Editor (buffer)

Constructs a new Editor instance, displaying the specified buffer. You would typically not use this directly, but instead create a new editor via Application.new_editor.

register_indicator (id, placement = ‘bottom_right’, factory = nil)

Registers an indicator with the specified id. Placement indicates where the indicator should be place. Possible values (strings) are:

An indicator is a simple label by default, but it’s possible to add an arbitrary widget as an indicator via the factory parameter. If specified, factory must be a callable object that when called returns a Gtk widget.

unregister_indicator (id)

Unregisters the indicator with the specified id.

Methods

backward_to_match (str)

Moves the cursor backwards to the next reverse match of str, within the current line. Does nothing if str could not be found.

comment ()

Comments the current line or selection, if possible, by forwarding the request to the current mode.

complete ()

Starts a completion at the current cursor position.

copy_line ()

Copies the current line to the clipboard.

delete_back ()

Deletes the preceeding character, if one is present. With a selection present, deletes the selection.

delete_forward ()

Deletes the the current character, if one is present. With a selection present, deletes the selection.

delete_line ()

Deletes the current line.

delete_to_end_of_line ()

Deletes from the current cursor column to the end of the current line.

duplicate_current ()

Duplicates the current line if no selection is present. With a selection present, duplicates the text included in the selection.

forward_to_match (str)

Moves the cursor forward to the next match of str, within the current line. Does nothing if str could not be found in the remainder of the line.

grab_focus ()

Grabs focus for the specified editor, i.e. causes the editor to be focused.

indent ()

Indents the current line or selection, if possible, by forwarding the request to the current mode.

indent_all ()

Indents all lines in the current buffer if possible, by selecting all lines and forwarding the request to the current mode.

insert (text)

Inserts text at the current cursor position.

join_lines ()

Joins the current line with the following line. Any space between the two lines is collapsed to one space. The cursor is positioned at the end of the current line, as it was before the join.

new_line ()

Inserts a new line at the current cursor position.

paste (opts = {})

Pastes the current contents of the clipboard, or a specific clipboard item, at the current cursor position. opts is an optional table of options. It currently can contain the following options:

redo ()

Redo:s the last undone edit operation, if any.

remove_popup ()

Removes any popup currently showing for the editor.

scroll_down ()

Scrolls the editor window down one line, if possible. I.e. causes the line below the currently last showing line to be visible.

scroll_up ()

Scrolls the editor window up one line, if possible. I.e. causes the line before the currently first showing line to be visible.

shift_left ()

If a selection is present, shift the entire selection one indent level to the left. With no selection present, the current line is shifted one indentation level to the left.

shift_right ()

If a selection is present, shift the entire selection one indent level to the right. With no selection present, the current line is shifted one indentation level to the right.

show_popup (popup, options = {})

Display the popup for the specific editor. The popup is displayed at the current cursor position, unless otherwise specified in options. The can only be one popup for a given editor at one time, invoking show_popup when an existing popup is active will cause that popup to close.

options can contain the following keys:

smart_tab ()

Inserts a tab if no selection is present, and indents the current selection on indentation level to the right if a selection is present.

The behaviour in the first case is dependent on several configuration variables.

smart_back_tab ()

Dedents the current selection on indentation level to the left if a selection is present.

If a selection is not present, then:

to_gobject ()

Returns the Gtk view for the Editor.

toggle_comment ()

Comments or uncomments the current line or selection, if possible, by forwarding the request to the current mode.

transform_active_lines (f)

A helper for transforming .active_lines within the scope of Buffer.as_one_undo for the current buffer. Invokes f with .active_lines, with any modifications being recorded as one undo operation.

uncomment ()

Uncomments the current line or selection, if possible, by forwarding the request to the current mode.

undo ()

Undo:s the last edit operation, if any.

with_position_restored (f)

Invokes f, and restores the position to the original line and column after f has returned. Should the indentation level for the current line have changed, attempts to automatically adjust the column for the new indentation.

- -
- - - diff --git a/site/source/versions/0.3/doc/index.html b/site/source/versions/0.3/doc/index.html deleted file mode 100644 index 4b3aa45db..000000000 --- a/site/source/versions/0.3/doc/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - Howl :: Howl 0.3 Documentation - - - - - - - -
-
- -
- -

Howl 0.3 Documentation

-
This is the documentation for the 0.3* releases of Howl. To view the latest documentation, please visit the main documentation page
- -

User manual

1 Getting started

2 Configuring Howl

3 Using Howl completions

4 Working with files

5 Editing

6 Using multiple views

7 Running external commands

8 What's next?

API reference

(.. WIP)

Howl specs (tests)

Below are the Howl specs in HTML format. While the specs are certainly not complete, they are provided here in the hope that they may be useful for better understanding the API, as well as providing some code examples. Bundle specs are currently not included in the below list.

- -
- - - diff --git a/site/source/versions/0.3/doc/manual/completions.html b/site/source/versions/0.3/doc/manual/completions.html deleted file mode 100644 index 1ff7f24ed..000000000 --- a/site/source/versions/0.3/doc/manual/completions.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - Howl :: Using Howl completions - - - - - - - -
-
- -
- -

Using Howl completions

-
- Using Howl completions -
- -
-

Overview

Howl is strongly in favor of completions, and will offer them whenever and wherever possible. This section aims to provide an overview of Howl completions work, and how to use them for best effect. Alternatively, if you consider auto-completions a nuisance and would like to cut down on them you’ll learn how to do that here as well.

Using completions

Interacting with completions

There are currently two different places where you’ll encounter completions: In editors while editing text, and in the command line while entering commands. While the completions offered differs as one would expect, the way you interact with a completion list is the same:

When completing, Howl will try to match your input string against the available completions in two ways: Exact matching and boundary matching. An exact match means that your input string is found as-is in the completion. A boundary match means that all parts of your input string matches at one or more boundaries in the completion, typically defined as underscores, slashes, etc. The below image illustrates the two different types of matches for a completion list:

Completion types

In the above example we can see that “aa” matches “attr_accessor” as a boundary match, while “mraardvark” is an exact match. The order of the completions above is no coincidence - boundary matches are preferred over exact matches.

Finally, a note about a gotcha:

As long as a completion list is showing, enter will always select the active completion. This is typically what you want. However, at times you just want add a new line, or enter the text as written. To avoid selecting the completion, enter escape to close the completion list first.

Configuring completions

Here are some configuration variables you might want to tweak in order to control completions:

Controls the mode of how completions are started. This is of interest particularly if you want turn off automatically shown completion lists. If you turn it off, you will have to explicitly request completions using the editor-complete command for editors (bound to ctrl_space by default) or by pressing tab in the command line.

Controls the number of completions shown in the completion list.

Whether a selected completion will replace the current word or not.

When auto-complete is one, this variable controls after how many characters the completion list should pop up after.

This variable, unset by default, contains a list of style patterns where the completion list should not automatically pop up.

For the in-buffer completer, this controls the number of open buffers that are consulted for completions.

For the in-buffer completer, this controls whether only open buffers with the same mode as the current one is consulted or not.

Next: Working with files

- -
- - - diff --git a/site/source/versions/0.3/doc/manual/configuration.html b/site/source/versions/0.3/doc/manual/configuration.html deleted file mode 100644 index 4615ac2aa..000000000 --- a/site/source/versions/0.3/doc/manual/configuration.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - Howl :: Configuring Howl - - - - - - - -
-
- -
- -

Configuring Howl

-
- Configuring Howl -
- -
-

Init files

Howl looks for a startup file in the Howl user directory: ~/.howl. It searches for either ~/.howl/init.lua or ~/.howl/init.moon. Which one to pick depends on your preference with regards to language - init.moon for Moonscript and init.lua for Lua. Should a startup file be found, it is loaded after Howl is initialized, which includes loading all available bundles. Howl does not have any special configuration format for use with the startup file, instead it’s just plain Lua or Moonscript. While the startup file would typically be mostly used for various type of configuration, there’s no restriction to what you can do in it - you have access to the entire Howl API.

You can split up your startup code in multiple files if you like. Your local user files will be not found by an ordinary require, since the user directory is not part of the search path. However, there is an user_load helper available from your startup files that works the same way. For example, given init.moon and other.moon in the Howl user directory, you could load ‘other’ from init like so:

other = user_load 'other'

Just as with require, paths are given without any extension. Files are loaded only once, with subsequent loads returning the same value. The path passed to user_load can contain dots, which are translated to the directory separator before loading the file.

It is not allowed for the startup files to implicitly clobber the global environment, and Howl will raise an error upon startup if this is detected. Consider for instance this incorrect Lua startup file:

-- Oops, forgot the local keyword here
my_internal_var = 2

This would cause Howl to abort with an error upon startup. Should you for any reason want to set a global variable, you can do so by being explicit:

_G.my_explicit_global = 2

(Note: the user_load helper is only available when loading startup files.)

Configuration variables

Overview

Things that are meant to be configurable in Howl are exposed as “configuration variables”. Configuration variables can be set either interactively from within Howl, using the set command, or programmatically from code. To get an overview of currently available variables, type set and press space at the command line to view a list.

Configuration variables can be specified at three different levels in Howl, in ascending order of priority:

The value set for the variable is used unless overridden by a mode or buffer specific setting (the set command always sets variables globally).

The value is set for a particular mode (e.g. “Lua” or “Ruby”), and is applied whenever a buffer with that particular mode is active. The value is used unless overridden by a buffer specific setting, and overrides any global setting.

The value is set for a particular buffer, and is applied whenever that buffer is active. The value overrides any mode specific or global setting.

As an example of how this could be used a real life scenario, consider the case of indentation: You might generally prefer your source code to be indented with two spaces. However, some languages might have generally accepted style guidelines where four spaces is considered the norm. Even so, certain projects written in such a language might have adopted the inexplicable custom of using three spaces for indentation.

In such a scenario, you could set the indent variable to 2 globally, override it with 4 for a given mode, and override with 3 for any buffer with an associated file in a certain directory.

Programmatic access

As described above, variables can be set on three different levels. No matter the on what level they’re set, they’re always set (and accessed) using config objects. For global accesses, you can use the main config object in the howl namespace. For mode variables you access variables using the config object on a particular mode instance, and similarily for buffer variables you use the config object for a particular buffer.

The following code snippet illustrates the various ways of setting variables on different levels:

howl.config.my_var = 'foo'
howl.mode.by_name('ruby').config.my_var = 'foo'
howl.app:new_buffer().config.my_var = 'foo'

Setting variables upon startup

Let’s have a look at configuring the indent variable as discussed in the overview, using the below example Moonscript init file (init.moon):

import config, mode, signal from howl
import File from howl.fs

-- Set indent globally to two spaces
config.indent = 2

-- Use four spaces for C files
mode.configure 'c', {
  indent: 4
}

-- Hook up a signal handler to set it to three for this weird project
that_project_root = File '/home/nino/code/that_project'

signal.connect 'file-opened', (args) ->
  if args.file\is_below that_project_root
    args.buffer.config.indent = 3

A few notes on the above example:

Key bindings

Key bindings map keyboard presses to different actions within Howl. The nitty-gritty details on how this is handled is outlined in the documentation for the bindings module, and won’t be repeated here. Rather, the below Lua example illustrates how to add different kind of binding customizations from within your init file (init.lua).

howl.bindings.push {
  -- editor specific bindings
  editor = {
    -- bind ctrl_k to a named command
    ctrl_k = 'editor-cut-to-end-of-line',

    -- bind ctrl_shift_x to a closure
    ctrl_shift_x = function(editor)
      -- replace the active chunk with a reversed bracked enclosed version
      editor.active_chunk.text = "<" .. editor.active_chunk.text.ureverse .. ">"
    end
  },

  -- Bind the Emacs find-file binding (C-x C-f) to the open command
  ctrl_x = {
    ctrl_f = 'open'
  }
}

Running commands

You’ve seen how to invoke commands from a key binding (simply specify the command name as a string), but sometimes you’ll want to invoke commands programmatically from within your startup file. As an example, to enter VI mode automatically upon startup:

howl.command.vi_on!

Consult the documentation for the command module for more information.

Next: Using Howl completions

- -
- - - diff --git a/site/source/versions/0.3/doc/manual/editing.html b/site/source/versions/0.3/doc/manual/editing.html deleted file mode 100644 index e6280a835..000000000 --- a/site/source/versions/0.3/doc/manual/editing.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - Howl :: Editing - - - - - - - -
-
- -
- -

Editing

-
- Editing -
- -
-

Overview

This section attempts to highlight a few things that you might encounter while editing, or that might simplify your editing experience.

Auto pairs

Auto pairs, where a matching end character is inserted automatically as you type the starting character, is supported out of the box in Howl. This can save you some typing as you don’t have to type out the ending character for every combination of [], {}, etc. If you select some text and type an auto pair character such as [, auto pairs will enclose the selection in matching start and end characters. Exactly what pairs are enabled for a buffer is specified by the buffer’s mode.

In case you find auto pairs annoying, the configuration variable auto_pair lets you specify whether you want this on or not.

Code blocks

Code blocks are code snippets that are automatically inserted as you type. They differ from completions in that they are inserted without any prompting, and can include more text than a single completion would. As an example, if you were to type the following Lua, and press enter:

function foo() -- <- cursor here

Howl would automatically insert the matching end for you, like so:

function foo()
  -- <- cursor here
end

The configuration variable auto_format lets you specify whether you want this on or not.

Buffer structure

When editing a larger buffer, it can be challenging to quickly jump to a specific part of it. The buffer-structure command (bound to alt_s by default) can provide you with an outline for the current buffer:

Buffer structure

How well this works is depending on the language mode - should the mode not provide custom support for this a general, indentation-based, structure is provided.

The buffer-search-forward and buffer-search-backward commands (bound to ctrl_f and ctrl_r respectively) provide an easy way to find exact matches near the cursor. The visible matches are highlighted in real-time, as you type your search text.

Buffer search

The match closest to the cursor is focused and you can use the up and down keys to jump between different matches. Hitting enter moves the cursor to the focused match.

Looking only for whole word matches can be useful when there happen to be many sub-string matches that you want to ignore. The buffer-search-word-forward and buffer-search-word-backward commands (bound to ctrl_period and ctrl_comma) work similar to the buffer search commands above, but they only match whole words and they also automatically search for the current word at the cursor.

Whole word search

Note that the match within ‘text_len’ is not highlighted in the screenshot above.

The up and down keys jump between the matches for these commands as well.

Buffer grep

Buffer grep works as an alternative to the regular buffer-search-forward command for searching for something in the current buffer. It let’s you grep all lines in the current buffer for a search string and displays all matching lines in real-time as you type:

Buffer grep

This is decidedly less effective than doing a plain search, which can be a factor for large buffers.

Replacement

The buffer-replace and buffer-replace-regex commands provide a way to replace multiple matches of some text or a regular expression in the current buffer.

The simpler buffer-replace command is used for replacing exact matches of some text. After invoking buffer-replace, you type the text you want to match (also called the target text), followed by / (the forward slash is the default separator), followed by the replacement text. As an example, if you want to replace all instances of ‘showing’ with ‘visible’, you would invoke buffer-replace and then type ‘showing/visible’.

Buffer replace

As you type, the displayed preview buffer is updated to show the effect of your replacement. You can use the up and down arrow keys to jump between different matches in the preview buffer. You can press alt_enter to toggle whether or not the currently focussed match should be replaced with the target - this lets you selectively preserve some matches from being replaced.

Once you are happy with the replacements as displayed in the preview buffer, you can press enter to commit the replacements - this updates the original buffer.

If you want to use ‘/’ as part of your target text, you need to use a different separator. To specify this, type backspace immediately after invoking buffer-replace - this deletes the automatically inserted leading ‘/’. Now type a separator of your choice (for example, ‘#’), followed by the target text, the chosen spearator, and then the replacement text.

The buffer-replace-regex command works similarly to buffer-replace but the target text is specified as a regular expression and not as an exact match. In addition, the replacement text can contain back-references to specific parts of the target. A back-reference is specified as ‘\’ followed by a number. For example, ‘\1’ refers to the first group in the matched text.

Selecting some text in the editor before invoking a replace command restricts the replacement to the selected text only.

Comments

The editor-toggle-comment is bound to ctrl_slash by default, and let’s you quickly comment and uncomment code.

Clipboard history

Howl manages its own clipboard, and lets you paste cut or copied text other than the latest text in the clipboard. The editor-paste.. command (bound to ctrl_shift_v by default) opens a list of previous clips and pastes any available clip that you choose:

Clipboard paste

Word wrapping

Howl provides optional support for hard wrapping of text paragraphs. The fill-paragraph command, bound to alt_q by default, will reflow a paragraph so that each line is at most as long as the configuration variable hard_wrap_column specifies.

You can also turn on automatic reflowing of paragraphs if you like, by customizing the auto_reflow_text configuration variable. This reflow the current paragraph as you type if needed. For example, the author keeps this in his ~/.howl/init.moon to enable automatic reflowing for markdown documents:

howl.mode.configure 'markdown', {
  auto_reflow_text: true
}

Unlike most other feature, this is not enabled by default, so you have to explicitly turn it on.

Version control diffs

The version control support in Howl is currently rather spartan, and will be expanded in future releases. However, if you’re using Git you might find the vc-diff and vc-diff-file commands useful. The former displays a complete diff for your entire repository, while the latter displays a diff for the current file.

Documentation popup

Support for this is dependent on the language mode, and is currently only available for Lua and Moonscript.

The show-doc-at-cursor command, bound to ctrl_q by default, pops up documentation for the symbol at the cursor if available:

Show doc


Next: Using multiple views

- -
- - - diff --git a/site/source/versions/0.3/doc/manual/files.html b/site/source/versions/0.3/doc/manual/files.html deleted file mode 100644 index 05cbdbec8..000000000 --- a/site/source/versions/0.3/doc/manual/files.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - Howl :: Working with files - - - - - - - -
-
- -
- -

Working with files

-
- Working with files -
- -
-

Opening files

Howl provides a text-oriented interface, and so you want see any traditional graphical open file dialogs. Instead you’ll open files from the command line, using commands. First off, the open command lets you navigate the file system and select a file to open. It’s bound to ctrl_o in the default keymap, and is also aliased as e for those more comfortable with VI. Triggering that command opens up the command line prompt and displays the contents of the current directory, as determined by the current buffer:

File open

Once you’re in the prompt, you can then select the file of your choice. You can choose the file from the list by manually navigating using the arrow keys, ctrl_p, ctrl_n, etc., if you want. However, it’s usually much faster to narrow down the list by typing something that matches the file you want. Just as with completions (as described in the previous section), your input string will be matched against the available files using boundary matching or exact matching. Once the selected file ends up at top, simply press enter to open it.

If the file you selected is a directory, the list and prompt will update itself for the selected directory, letting you pick a file there. On the other hand, if you want to go up a directory level, press backspace. For convenience, if you type ~/ or / at the start of a prompt, you will be directly transferred to your home directory and the root directory respectively.

Opening a file within a project

Navigating the file system and selecting a file for opening is all fine and good for the odd file you want to open. Most of the time however, you’re likely working within the context of a project of some sort. In that case it can quickly get tedious to navigate directories up and down, and especially for larger projects, since you might not even be entirely sure where a desired file is placed. Fortunately, Howl offers the project-open command to help with this.

Howl provides simple and light-weight support for projects. In Howl, a project is currently defined as root directory containing the project files below, with an optional version control system attached to it. The project-open command (bound to ctrl_p by default) provides a way of selecting a file to open from all the files contained in your project. Thanks to the matching capabilities, this often provides a much faster way of opening files than navigating the project directory structure do. Below you’ll see an example for the Howl project itself:

Project open

Saving buffers

Invoke the save command to save the current buffer to a file. If the buffer has an associated file, it will get saved to that file, and otherwise you’ll be prompted for the file name to save the buffer to. The save command is bound to ctrl_s in the default keymap, and is also aliased as w.

To save a buffer with an associated file to another file, invoke the save-as command (bound to ctrl_shift_s in the default keymap). There’s also a related command, save-and-quit, that allows you to save any modified buffers and exit Howl in one go.

Switching between open buffers

While Howl provides the ability to view more than one buffer at a time by supporting multiple open views, you’ll likely have more buffers open than you can fit on your screen. In order to switch to another buffer, you can use the switch-buffer command (bound to ctrl_b by default):

switch-buffer

This let’s you select an open buffer to display in the current view. The list as presented is ordered by access time, thus you’ll see your most recent buffers at the top with less recently used buffers below. As always, you can type to narrow down the list.

Another command that might prove useful to you is switch-to-last-hidden-buffer. This will switch to the most recently accessed buffer that is not currently showing in any view, and is thus useful for quickly switching between to related files in the same view.

Creating new buffers / files

So what do you do if you just want to create a new buffer, that will eventually get saved to a new file? Well, there is a new-buffer command available for this, which will create a new buffer without an associated file, that you can later save to a named file. This is not bound to any key by default however, and the reason for that is that it’s not considered that useful. Most of the time when you want to create a new file, you already know what the file should be named. And as is the case with some other editors, such as Emacs or Vim, it is not a requirement for a file to actually exists in order to successfully open it. Thus, if you want to create a new file whose name you already know, just open the file using the open command and enter the new name of the file.

If this sounds strange to you, consider that a buffer and a file are two different entities, and that a buffer only has an association with a file. So when you open a non-existing file, you create a new buffer with an association to the specified file, which does not have to exist. As you save the buffer, the file will be created as necessary.

Next: Editing

- -
- - - diff --git a/site/source/versions/0.3/doc/manual/getting-started.html b/site/source/versions/0.3/doc/manual/getting-started.html deleted file mode 100644 index 5fc3c56b6..000000000 --- a/site/source/versions/0.3/doc/manual/getting-started.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - Howl :: Getting started - - - - - - - -
-
- -
- -

Getting started

-
- Getting started -
- -
-

So you’ve installed Howl, but how do you actually use it? (if you haven’t installed it yet, see the instructions here). As you might have read earlier, Howl is rather minimalistic when it comes to the user interface, and it prefers text-based interfaces over the traditional graphical ones. As such, you will not find the typical menu or toolbar that might otherwise help you get started with other applications. In this section we’ll look at the basic concepts of Howl, which will hopefully help you get a better understanding of what Howl is and how you can use it.

The visual components

To begin with, let’s examine the basic visual components that you see when you use Howl. Using one of the screen shots as an example:

Visual components

As per the above image, the three basic visual components are windows, views and the command line.

Windows

When you start up Howl you’ll see one window, containing everything else. You could potentially have multiple windows open for the same Howl instance, even though this is not well-tested at the current time.

Views (Editors)

A window can contain an arbitrary number of views, which are any type of graphical component. Currently there are only type of view available, called an “editor”. Editors are the source editing components you’ll work with most of the time. Editors themselves contain other visual components, such as header and footer components with “indicators” used for displaying for example the current position in the file. An editor always displays exactly one buffer. As can be seen in one of the screen shots it’s possible to have multiple views/editors along each other in the same window.

Command line

The command line component is where you enter your commands. As we will see, commands are the primary way of interacting with Howl, used for mostly anything within Howl. The command line allows you to input these commands, and provides completions as necessary.

Other basic concepts

Buffers

Buffers are what you work with when you edit. Buffers are typically associated with a file, used for storing the buffer contents on disk. This is not necessarily the case however, as buffers can just as well exist without any association to a given file (consider for instance the “Untitled” buffer you see when you first open Howl without passing any arguments). You can have as many buffers open as you want, only limited by the amount of available memory. You can choose to display a given buffer in an existing editor by switching buffers (via the switch-buffer command).

Modes

All buffers have a mode associated with them. Modes handles everything language/format specific for a certain buffer, such as indentation support, syntax highlighting, etc. Modes are typically assigned to a buffer automatically, e.g. when a file is opened a mode is automatically selected based on the file’s extension, etc.

Key bindings

Key bindings are used for triggering certain actions whenever a certain key combination is entered. Actions are typically commands, but can also be custom functions.

Signals

Signals are fired as a result of different actions within Howl, and provides a generic way of receiving notification. You could for instance register to be notified whenever a buffer was saved.

Entering commands

Manual entry

As said previously, most interactions with Howl will typically be the result of a command. So let’s gets started with manually entering some basic commands. To enter you first command, you need to open the command line. In the default keymap, this is “bound” (mapped) to the alt+x key combination, so enter that to open the command line. You should now see the command line being opened, awaiting your command. If you want to, press tab to bring up a completion list of available commands.

As an example, let’s open a file for editing. Enter open and press space to open a file. You will automatically be presented with completions. Navigate up and down the directory tree as needed, using backspace and enter, and press enter once you’ve found the file you wanted.

Using completions

Completions are available within the command line, using the tab key. Completions are enabled by default for most commands as you will see, but they are not automatically shown when entering commands. To explicitly request completions of the available commands, press tab. To cancel completions, press escape. For commands that want some kind of hierarchal input, such as file commands, pressing backspace when at the beginning of a prompt allows you to move up in the hierarchy.

The completion list will automatically filter itself to match whatever you type in the command line.

Using keyboard shortcuts

Manually entering commands is typically not something you want to do for commands that you invoke often. Unsurprisingly, any command can be bound to a key combination as well. Howl comes with a default keymap for the most basic bindings (not complete by any measure, so please suggest missing additions). So in the previous example, you could have more quickly opened a file using the ctrl+o key binding. Assigning your own combinations is easy, and will be discussed later on in the manual. Note: If you bring up the completion list at the command prompt, you’ll see that it includes the key bindings for the listed commands when available.

VI users:

Howl ships with a basic VI bundle, which you can activate with the vi-on command. It’s rather incomplete at this point and will be improved, but contains at least some of the basic editing functionality.

Next: Configuring Howl

- -
- - - diff --git a/site/source/versions/0.3/doc/manual/next.html b/site/source/versions/0.3/doc/manual/next.html deleted file mode 100644 index 7442fd8bf..000000000 --- a/site/source/versions/0.3/doc/manual/next.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: What's next? - - - - - - - -
-
- -
- -

As noted at the top of the documentation index, the documentation is not complete. This means that there’s a lot more interesting stuff about Howl that has yet to documented, both for the API documentation and the manual. This includes things such as Howl’s bundle system and how you can write your own bundles, how to add support for a new language, how to create a new theme, etc. Unfortunately there are only so many hours in a day.

Meanwhile, if this looks interesting to you, then dive in! Don’t be afraid of browsing through the source to see what’s there. If you’re wondering about how something works, have a look to see if there’s any spec that covers it. And you can always get in contact.


.. Back to the documentation index.

- -
- - - diff --git a/site/source/versions/0.3/doc/manual/running_commands.html b/site/source/versions/0.3/doc/manual/running_commands.html deleted file mode 100644 index f94fc23f8..000000000 --- a/site/source/versions/0.3/doc/manual/running_commands.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - Howl :: Running external commands - - - - - - - -
-
- -
- -

Running external commands

-
- Running external commands -
- -
-

Overview

While most of the time spent developing is likely to be editing, there’s often a need for running external commands, such as compilers, test, etc., as part of the work flow. Howl provides two different commands for this purpose, exec and project-exec, bound to ctrl_shift_r and ctrl_alt_r respectively. They both work the same way, allowing you execute a command of your choice from within a directory, displaying any output in a buffer. The difference is that project-exec starts out from from the root of your current project directory, while exec starts out in the directory associated with the current buffer.

Interacting with the prompt

Exec prompt

Upon executing one of the above commands, you’ll end up in the prompt. The prompt offers specific completions and ways of making it easier to input your command. Just as with the ordinary prompt for opening a file, you can enter backspace to move up one directory level. Entering ~ and / allows you to quickly run a command from your home directory, or the root directory, respectively. Completions are available both for commands themselves as well as arguments, and support completion of arguments spanning multiple directory levels (e.g. ./my-dir/sub-dir/foo).

As a final convenience, the prompt supports an internal cd command, allowing you to move to a different directory within the prompt.

Running commands

Once you have typed your command, you can run it by pressing enter. Both the exec and project-exec commands will launch the specified command in the directory displayed in the prompt, using your shell. The fact that your shell is used for this allows for the use of any ordinary shell aliases you normally use (provided that they are available for non-login shells), as well as shell constructs such as for loops, etc.

The command thus launched will be associated with a new buffer, in which any output from the command will be displayed. Commands will not block the editor while running, so you’re free to resume your other tasks while the the command runs. There is also no limitation on the number of concurrently executing commands you might have - they will all be associated with their own buffers that you can switch between as you please, as illustrated by the below image.

Concurrent commands

Also illustrated by the above image is the fact that Howl adds some extra support for displaying a command’s output, with the example in question showing off Howl’s support for ANSI color escape codes. For less fanciful commands Howl will display any standard output plainly, while error output will be shown in a different style to allow you to quickly differentiate between the two.

project-build

Howl features another execution command, project-build, bound to ctrl_shift_b. This is the same as project-exec, but it executes the command defined in config.project_build_command.

Dealing with rogue commands

While a well behaved command will exit on its own, occasionally there are those that need an helping hand. Pressing Ctrl + c when in a process buffer will send the SIGINT signal to the currently running process, hopefully hastening its way towards a graceful exit (Ctrl + c while a selection is active will still only copy the selection). For the obstinate cases, Ctrl + backslash can be used to send the SIGKILL signal.


Next: What’s next?

- -
- - - diff --git a/site/source/versions/0.3/doc/manual/views.html b/site/source/versions/0.3/doc/manual/views.html deleted file mode 100644 index 4005f39bc..000000000 --- a/site/source/versions/0.3/doc/manual/views.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - Howl :: Using multiple views - - - - - - - -
-
- -
- -

Using multiple views

-
- Using multiple views -
- -
-

Overview

When you start Howl, you’ll be presented with one window, containing one editor view. Assuming there is sufficient screen estate to spare, it’s often desirable to have multiple views open in the same window, which let’s you view one or more buffers simultaneously. This is supported in Howl, where windows can contain an arbitrary number of views, arranged in a grid pattern.

Multiple views

In the above picture you have two editors in the first row, each occupying one column each. In the bottom row you see one editor view occupying two columns. While the most you’ll likely ever want is around two or three separate view, you can divide windows up in unreasonable ways should you so desire:

Lots of views

There is currently no way to manually resize views; views are reflowed to fill the entire window, and will occupy the maximum amount of space available to them.

View commands

Below is a list of some useful commands that work with views:

Creating views

Each of the four directional commands above (view-left, view-right, view-up and view-down) have two additional companion commands:

These commands will wrap around the grid if no view could be found in the specified direction. The view-right-wraparound command for instance would go to the view to the right, should it exist. If not, it would go to first view in the next row should that exist, and to the first view of the first row if not.

These will automatically create a new view in the specified direction, if necessary. For instance, the view-right-or-create command would go to the view to the right if there was a view to the right. Should no such view exist however, it would be created first. The last set of commands are bound to shift_alt_left + <arrow key> in the default keymap.

Manipulating views


Next: Running external commands

- -
- - - diff --git a/site/source/versions/0.3/doc/spec/application_spec.html b/site/source/versions/0.3/doc/spec/application_spec.html deleted file mode 100644 index 04053aa20..000000000 --- a/site/source/versions/0.3/doc/spec/application_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Application - - - - - - - -
-
- -
- -

howl.Application

local root_dir, application

before_each ->
  root_dir = File.tmpdir!
  application = Application root_dir, {}

after_each -> root_dir\delete_all!

.buffers are sorted by visibility status and last_shown

sci = Scintilla!
hidden_buffer = application\new_buffer!
hidden_buffer.title = 'hidden'

last_shown_buffer = application\new_buffer!
editor = Editor last_shown_buffer
last_shown_buffer.title = 'last_shown'

visible_buffer = application\new_buffer!
editor.buffer = visible_buffer
visible_buffer.title = 'visible'

buffers = [b.title for b in *application.buffers]
assert.same { 'visible', 'last_shown', 'hidden' }, buffers

new_buffer(mode)

creates a new buffer with mode

m = mode.by_name 'default'
buffer = application\new_buffer m
assert.equal m, buffer.mode

uses the default mode if no mode is specifed

buffer = application\new_buffer!
assert.equal 'default', buffer.mode.name

registers the new buffer in .buffers

buffer = application\new_buffer!
assert.same { buffer }, application.buffers

open_file(file, editor)

editor = Editor Buffer {}

opens the file in the specified editor if given

File.with_tmpfile (file) ->
  file.contents = 'well hello there'
  application\open_file file, editor
  assert.equal file.contents, editor.buffer.text

returns the newly created buffer

File.with_tmpfile (file) ->
  buffer = application\open_file file, editor
  assert.equal buffer, editor.buffer

adds the buffer to @buffers

File.with_tmpfile (file) ->
  buffer = application\open_file file, editor
  assert.same { buffer }, application.buffers

fires the file-opened signal

with_signal_handler 'file-opened', nil, (handler) ->
  File.with_tmpfile (file) ->
    application\open_file file, editor
  assert.spy(handler).was_called!

(when <file> is already open)

switches to editor to the existing buffer instead of creating a new one

with_tmpdir (dir) ->
  a = dir / 'a.foo'
  b = dir / 'b.foo'
  buffer = application\open_file a, editor
  application\open_file b, editor
  application\open_file a, editor
  assert.equal 2, #application.buffers
  assert.equal buffer, editor.buffer

synchronize()

(when a buffer's file has changed on disk)

local b

before_each ->
  reload = spy.new -> nil
  b = application\new_buffer!
  b.reload = reload
  rawset b, 'modified_on_disk', true

the buffer is reloaded automatically if it is not modified

application\synchronize!
assert.spy(b.reload).was_called!

the buffer is not reloaded automatically if it is modified

b.modified = true
application\synchronize!
assert.spy(b.reload).was_not_called!
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/aux/destructor_spec.html b/site/source/versions/0.3/doc/spec/aux/destructor_spec.html deleted file mode 100644 index 95a8dff9d..000000000 --- a/site/source/versions/0.3/doc/spec/aux/destructor_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.aux.destructor - - - - - - - -
-
- -
- -

howl.aux.destructor

returns an object, for which <callback> is called when it is collected

callback = Spy!
destructor callback
collectgarbage!
assert.is_true callback.called

passes any additional arguments given to the callback

callback = Spy!
destructor callback, 1, 'foo'
collectgarbage!
assert.same callback.called_with, { 1, 'foo' }

each destructor is unique

callback = Spy!
other_callback = Spy!
destructor callback
destructor other_callback
collectgarbage!
assert.is_true callback.called
assert.is_true other_callback.called

defuse() disables the destructor

callback = Spy!
d = destructor callback
d.defuse!
collectgarbage!
assert.is_false callback.called
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/aux/lpeg_lexer_spec.html b/site/source/versions/0.3/doc/spec/aux/lpeg_lexer_spec.html deleted file mode 100644 index 7a5953fea..000000000 --- a/site/source/versions/0.3/doc/spec/aux/lpeg_lexer_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.aux.lpeg_lexer - - - - - - - -
-
- -
- -

howl.aux.lpeg_lexer

the module can be called directly to create a lexer (same as new())

assert.not_has_error -> l -> true

the resulting lexer can be called directly

lexer = l -> P'x' * Cp!
assert.same { 2 }, lexer 'x'

imports lpeg definitions locally into the module

for op in *{'Cp', 'Ct', 'S', 'P'}
  assert.is_not_nil l[op]

imports lpeg.locale definitions locally into the module

for ldef in *{'digit', 'upper', 'print', 'lower'}
  assert.is_not_nil l[ldef]

new(definition)

accepts a function

assert.not_has_error -> l.new -> true

capture(style, pattern)

returns a LPeg pattern

assert.equal 'pattern', lpeg.type l.capture('foo', P(1))

the returned pattern produces the three captures <start-pos>, <style-name> and <end-pos> if <pattern> matches

p = l.capture 'foo', P'fo'
assert.same { 1, 'foo', 3 }, { p\match 'foobar' }

predefined helper patterns

.eol

matches and consumes new lines

assert.is_not_nil l.eol\match '\n'
assert.is_not_nil l.eol\match '\r'
assert.equals 2, (l.eol * Cp!)\match '\n'
assert.equals 3, (l.eol * Cp!)\match '\r\n'

assert.is_nil l.eol\match 'a'
assert.is_nil l.eol\match '2'

.float

matches and consumes various float representations

for repr in *{ '34.5', '3.45e2', '1.234E1', '3.45e-2', '.32' }
  assert.is_not_nil l.float\match repr

.hexadecimal

matches and consumes various hexadecimal representations

for repr in *{ '0xfeab', '0XDEADBEEF' }
  assert.is_not_nil l.hexadecimal\match repr

does not match illegal hexadecimal representations

assert.is_nil l.hexadecimal\match '0xCDEFG'

.hexadecimal_float

matches and consumes various hexadecimal float representations

for repr in *{ '0xfep2', '0XAP-3' }
  assert.is_not_nil l.hexadecimal_float\match repr

does not match illegal hexadecimal representations

assert.is_nil l.hexadecimal_float\match '0xFGp3'

.octal

matches and consumes octal representations

assert.is_not_nil l.octal\match '0123'

does not match illegal octal representations

assert.is_nil l.octal\match '0128'

.line_start

matches after newline or at start of text

assert.is_not_nil l.line_start\match 'x'
assert.is_not_nil (l.eol * l.line_start * P'x')\match '\nx'

does not consume anything

assert.equals 2, (l.eol * l.line_start * Cp!)\match '\nx'

any(list)

the resulting pattern is an ordered match of any member of <list>

p = l.any { 'one', 'two' }
assert.is_not_nil p\match 'one'
assert.is_not_nil p\match 'two'
assert.is_nil p\match 'three'

<list> can be vararg arguments

p = l.any 'one', 'two'
assert.is_not_nil p\match 'two'

sequence(list)

the resulting pattern is a chained match of all members of <list>

p = l.sequence { 'one', 'two' }
assert.is_nil p\match 'one'
assert.is_nil p\match 'two'
assert.is_not_nil p\match 'onetwo'
assert.is_nil p\match 'Xonetwo'

<list> can be vararg arguments

p = l.sequence 'one', 'two'
assert.is_not_nil p\match 'onetwo'

word(list)

grammar = P {
  V'word' + P(1) * V(1)
  word: l.word { 'one', 'two2' }
}

returns a pattern who matches any word in <list>

assert.is_not_nil grammar\match 'one'
assert.is_not_nil grammar\match 'so one match'
assert.is_not_nil grammar\match '!one'
assert.is_not_nil grammar\match 'one()'
assert.is_not_nil grammar\match 'then two2,'
assert.is_nil grammar\match 'three'

only matches standalone words, not substring occurences

assert.is_nil grammar\match 'fone'
assert.is_nil grammar\match 'one2'
assert.is_nil grammar\match 'two2fold'
assert.is_nil grammar\match 'two2_fold'

accepts var arg parameters

assert.is_not_nil l.word('one', 'two')\match 'two'

span(start_p, stop_p [, escape_p])

p = l.span('{', '}') * Cp!

matches and consumes from <start_p> up to and including <stop_p>

assert.equals 3, p\match '{}'
assert.equals 5, p\match '{xx}'

always considers <EOF> as an alternate stop marker

assert.equals 3, p\match '{x'

allows escaping <stop_p> with <escape_p>

p = l.span('{', '}', '\\') * Cp!
assert.equals 5, p\match '{\\}}'

paired(p, escape [, pair_style, content_style])

p = l.paired(1) * Cp!

matches and consumes from <p> up to and including the matching <p>

assert.equals 3, p\match '||x'
assert.equals 5, p\match '|xx|x'

always considers <EOF> as an alternate stop marker

assert.equals 3, p\match '|x'

allows escaping the end delimiter with <escape>

p = l.paired(1, '\\') * Cp!
assert.equals 5, p\match '|\\|| foo\\'

(when pair_style and content_style are specified)

captures the components in the specified styles

p = l.paired(1, nil, 'keyword', 'string')
expected = {
  1, 'keyword', 2,
  2, 'string', 5,
  5, 'keyword', 6,
}
assert.same expected, { p\match '|foo|' }

still handles escapes properly

p = l.paired(1, '%', 'keyword', 'string')
expected = {
  1, 'keyword', 2,
  2, 'string', 6,
  6, 'keyword', 7,
}
assert.same expected, { p\match '|f%|o|' }

back_was(name, value)

p = Cg(l.alpha^1, 'group') * ' ' * l.back_was('group', 'foo')

matches if the named capture <named> previously matched <value>

assert.is_not_nil p\match 'foo '

does not match if the named capture <named> did not match <value>

assert.is_nil p\match 'bar '

produces no captures

assert.equals 1, #{ p\match 'foo ' }

last_token_matches(pattern)

matches if the last non-blank token matches pattern

p = l.blank^0 * l.digit^1 * l.blank^0 * l.last_token_matches(l.digit)
assert.is_not_nil p\match '123 '
assert.is_not_nil p\match '123 \t '
assert.is_not_nil p\match ' 123 '
assert.is_not_nil p\match ' 1 '
assert.is_not_nil p\match '1 '
assert.is_not_nil p\match '1 '
assert.is_not_nil p\match ' 1'

match_back(name)

p = Cg(P'x', 'start') * 'y' * l.match_back('start')

matches the named capture given by <name>

assert.equals 4, p\match 'xyxzx'

produces no captures

assert.equals 1, #{ p\match 'xyxzx' }

scan_until(stop_p [, escape_p])

matches until the specified pattern or <EOF>

assert.equals 3, (l.scan_until('x') * Cp!)\match '12x'
assert.equals 4, (l.scan_until('x') * Cp!)\match '123'

allows escaping <stop_p> with <escape_p>

p = l.scan_until('}', '\\') * Cp!
assert.equals 4, p\match '{\\}}'

scan_to(stop_p [, escape_p])

matches until the specified pattern or <EOF>

assert.equals 4, (l.scan_to('x') * Cp!)\match '12x'
assert.equals 4, (l.scan_to('x') * Cp!)\match '123'

allows escaping <stop_p> with <escape_p>

p = l.scan_to('}', '\\') * Cp!
assert.equals 5, p\match '{\\}}'

scan_through_indented

p = P' ' * l.scan_through_indented! * Cp!

matches until the indentation is smaller or equal to the current line

assert.equals 4, p\match ' x\n y'
assert.equals 8, p\match ' x\n  y\n z'

matches until eol if it can not find any line with smaller or equal indentation

assert.equals 7, p\match ' x\n  y'

uses the indentation of the line containing eol if positioned right at it

p = l.eol * l.scan_through_indented! * Cp!
assert.equals 8, p\match ' x\n  y\n z', 3

scan_until_capture(name, escape, [, halt_at, halt_at_N, ..])

matches until the named capture

p = Cg('x', 'start') * l.scan_until_capture('start')
assert.equals 4, p\match 'xyzx'

stops matching at any optional halt_at parameters

p = Cg('x', 'start') * l.scan_until_capture('start', nil, 'z')
assert.equals 3, p\match 'xyzx'

treats all stop parameters as strings and not patterns

p = Cg('x', 'start') * l.scan_until_capture('start', nil, '%w')
assert.equals 4, p\match 'xyz%w'

does not halt on escaped matches

p = Cg('x', 'start') * l.scan_until_capture('start', '\\', 'z')
assert.equals 7, p\match 'xy\\x\\zx'

matches until eof if no match is found

p = Cg('x', 'start') * l.scan_until_capture('start')
assert.equals 4, p\match 'xyz'

match_until(stop_p, p)

p = l.match_until('\n', C(l.alpha)) * Cp!

matches p until stop_p matches

assert.same { 'x', 'y', 'z', 4 }, { p\match 'xyz\nx' }

matches until eof if stop_p is not found

assert.same { 'x', 'y', 3 }, { p\match 'xy' }

complement(p)

matches if <p> does not match

assert.is_not_nil l.complement('a')\match 'b'
assert.is_nil l.complement('a')\match 'a'
assert.equals 3, (l.complement('a')^1 * Cp!)\match 'bca'

sub_lex_by_pattern(mode_p, mode_style, stop_p)

lexes any leading space followed by eol as extended whitespace

p = l.sub_lex_by_pattern(l.alpha^1, 'keyword', '>')
res = { p\match 'xx \n123>' }
assert.same {
  1, 'keyword', 3,
  3, 'default:whitespace', 5,
  5, 'embedded', 8
}, res

(when no mode is found for the <mode_p> capture)

emits mode match styling and an embedded capture for the sub text

p = l.sub_lex_by_pattern(l.alpha^1, 'keyword', '>')
res = { p\match 'xx123>' }
assert.same {
  1, 'keyword', 3,
  3, 'embedded', 6
}, res

(when a mode matching the <mode_p> capture exists)

local p

before_each ->
  sub_mode = lexer: l -> capture('number', digit^1)
  mode.register name: 'dynsub', create: -> sub_mode
  p = l.P'<' * l.sub_lex_by_pattern(l.alpha^1, 'keyword', '>')

after_each ->
  mode.unregister 'dynsub'

emits mode match styling and rebasing instructions to the styler

assert.same {
  2, 'keyword', 8,
  8, {}, 'dynsub|embedded'
}, { p\match '<dynsub>' }

lexes the content using that mode's lexer until <stop_p>

assert.same {
  2, 'keyword', 8,
  8, { 1, 'number', 4 }, 'dynsub|embedded'
}, { p\match '<dynsub123>' }

sub_lex(mode_name, stop_p)

lexes any leading space followed by eol as extended whitespace

p = l.sub_lex('unknown', '>')
res = { p\match ' \n123>' }
assert.same {
  1, 'default:whitespace', 3,
  3, 'embedded', 6
}, res

(when no mode is found matching <mode_name>)

captures using the embedded style until stop_p

p = l.sub_lex('unknown', '>')
res = { p\match 'xx>' }
assert.same {1, 'embedded', 3}, res

(when a mode matching <mode_name> exists)

local p

before_each ->
  sub_mode = lexer: l -> capture('number', digit^1)
  mode.register name: 'sub', create: -> sub_mode
  p = l.sub_lex('sub', '>')

after_each ->
  mode.unregister 'sub'

sub_captures_for = (text) ->
  res = { p\match text }
  res[2]

emits rebasing instructions to the styler

assert.same { 1, {}, 'sub|embedded' }, { p\match '' }

lexes the content using that mode's lexer until <stop_p>

assert.same {1, 'number', 3}, sub_captures_for '12>'

lexes until EOF if <stop_p> is not found

assert.same {1, 'number', 3}, sub_captures_for '12'

compose(base_mode, pattern)

returns a conjunction pattern with <pattern> and the mode pattern

base_mode = lexer: l -> capture('number', digit^1)
mode.register name: 'base_mode', create: -> base_mode
p = l.compose('base_mode', l.capture('override', l.alpha))^0
assert.same {
  1, 'override', 2,
  2, 'number', 3
}, { p\match 'a2' }

built-in lexing support

automatically lexes whitespace

lexer = l -> P'peace-and-quiet'
assert.same { 1, 'whitespace', 3 }, lexer ' \n'

automatically skips non-recognized tokens

lexer = l -> capture 'foo', P'foo'
assert.same { 2, 'foo', 5 }, lexer '|foo'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/aux/moon/propertyobject_spec.html b/site/source/versions/0.3/doc/spec/aux/moon/propertyobject_spec.html deleted file mode 100644 index f2c57b4d7..000000000 --- a/site/source/versions/0.3/doc/spec/aux/moon/propertyobject_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.aux.moon.PropertyObject - - - - - - - -
-
- -
- -

howl.aux.moon.PropertyObject

allows specifying a getter and setter using get and set

value = 'hello'
class Test extends PropertyObject
  @property foo:
    get: => value
    set: (v) => value = v

o = Test!
assert.equal o.foo, 'hello'
o.foo = 'world'
assert.equal o.foo, 'world'

assigning a property with only a getter raises a read-only error

class Test extends PropertyObject
  @property foo: get: => 'foo'

o = Test!
assert.raises 'read%-only', -> o.foo = 'bar'
assert.equal o.foo, 'foo'

two objects of the same class have the same metatable

class Test extends PropertyObject
  @property foo: get: => 'foo'

assert.equal getmetatable(Test!), getmetatable(Test!)

two objects of different classes have different metatables

class Test1 extends PropertyObject
  @property foo: get: => 'foo'

class Test2 extends PropertyObject
  @property foo: get: => 'foo'

assert.is_not.equal getmetatable(Test1!), getmetatable(Test2!)

meta methods are defined via the @meta function

class Test extends PropertyObject
  @meta __add: (o1, o2) -> 3 + o2

assert.equal 5, Test! + 2

inheritance

properties defined in any part of the chain works

class Parent extends PropertyObject
  new: (foo) =>
    super!
    @_foo = foo

  @property foo:
    get: => @_foo or 'wrong'
    set: (v) => @_foo = v .. @foo

class SubClass extends Parent
  new: (text) => super text

  @property bar:
    get: => @_bar
    set: (v) => @_bar = v

parent = Parent 'parent'
assert.equal parent.foo, 'parent'
parent.foo = 'hello '
assert.equal parent.foo, 'hello parent'

s = SubClass 'editor'
assert.equal s.foo, 'editor'
s.foo = 'hello'
assert.equal s.foo, 'helloeditor'
s.bar = 'world'
assert.equal s.bar, 'world'

overriding methods work

class Parent extends PropertyObject
  foo: => 'parent'

class SubClass extends Parent
  foo: => 'sub'

assert.equal SubClass!\foo!, 'sub'

write to read-only properties are detected

class Parent extends PropertyObject
  @property foo: get: => 1

class SubClass extends Parent
  true

assert.raises 'read%-only', -> SubClass!.foo = 'bar'

meta methods defined in any part of the chain works

class Parent extends PropertyObject
  @meta __add: (o1, o2) -> 3 + o2

class SubClass extends Parent
  @meta __div: (o1, o2) -> 'div'

o = SubClass!
assert.equal 5, o + 2
assert.equal 'div', o / 2

delegation

allows delegating to a default object passed in the constructor

target = {
  foo: 'bar'
  func: spy.new -> 'return'
}

class Delegating extends PropertyObject
  new: => super target
  @property frob: get: => 'nic'

o = Delegating!
assert.equals 'nic',  o.frob
assert.equals 'bar',  o.foo
assert.equals 'return',  o\func!
assert.spy(target.func).was.called_with target
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/aux/property_table_spec.html b/site/source/versions/0.3/doc/spec/aux/property_table_spec.html deleted file mode 100644 index 1d1e5be01..000000000 --- a/site/source/versions/0.3/doc/spec/aux/property_table_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.aux.PropertyTable - - - - - - - -
-
- -
- -

howl.aux.PropertyTable

returns a table with the properties in the passed table

pt = PropertyTable foo:
  get: (t) -> t.value
  set: (t, v) -> t.value = v

assert.is_nil pt.foo
pt.foo = 'hello'
assert.equal pt.foo, 'hello'

non-property key accesses return nil by default

assert.is_nil PropertyTable({}).foo

non properties can be accessed in the normal fashion

pt = PropertyTable {
  foo:
    get: -> 'foo'
  bar: -> 'bar'
  frob: 'frob'
}

assert.equal pt.foo, 'foo'
assert.equal pt.frob, 'frob'
assert.equal pt.bar!, 'bar'

pt.frob = 'froz'
assert.equal pt.frob, 'froz'

writing to a non-property key sets the value

t = PropertyTable {}
t.foo = 'bar'
assert.equal t.foo, 'bar'

writing to a read-only property raises an error

assert.error -> PropertyTable(foo: get: -> 'bar').foo = 'frob'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/aux/sandbox_spec.html b/site/source/versions/0.3/doc/spec/aux/sandbox_spec.html deleted file mode 100644 index 8abf82e7e..000000000 --- a/site/source/versions/0.3/doc/spec/aux/sandbox_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.aux.Sandbox - - - - - - - -
-
- -
- -

howl.aux.Sandbox

allows running a function with a specified environment

box = Sandbox foo: -> 'bar!'
assert.equal 'bar!', box -> return foo!

allows passing parameters to the function

box = Sandbox!
f = (...) -> return ...
assert.equal 'bar!', box(f, 'bar!')

.put allows modifiying the environment

box = Sandbox!
box\put what: -> 'bar!'
assert.equal 'bar!', box -> return what!

allows global access by default

box = Sandbox!
assert.equal table, box -> return table

disallows global access if options.no_globals is set

box = Sandbox nil, no_globals: true
assert.is_nil box -> return table

(when options.no_implicit_globals is set)

raises an error upon implicit global writes

box = Sandbox nil, no_implicit_globals: true
renegade = -> export frob = 'bar!'
assert.raises 'implicit global', -> box renegade

(when options.no_implicit_globals is not set)

collects exports into .exports

box = Sandbox!
box -> export foo = 'bar'
assert.equal box.exports.foo, 'bar'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/aux/sandboxed_loader_spec.html b/site/source/versions/0.3/doc/spec/aux/sandboxed_loader_spec.html deleted file mode 100644 index dd02cb6cd..000000000 --- a/site/source/versions/0.3/doc/spec/aux/sandboxed_loader_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.aux.SandboxedLoader - - - - - - - -
-
- -
- -

howl.aux.SandboxedLoader

local loader, dir

before_each ->
  dir = File.tmpdir!
  loader = SandboxedLoader dir, 'foo', {}

after_each -> dir\rm_r!

returns a Sandbox

assert.equals 'Sandbox', typeof loader

(exposed sandbox helpers)

<name>_file(rel_path)

returns a File object for the given file

f = dir\join('file.lua')
assert.equals dir\join('file.lua'), loader -> foo_file 'file.lua'

<name>_load(rel_basename)

loads relative bytecode, lua and moonscript files

dir\join('aux_lua.lua').contents = '_G.loaded_lua = true'
dir\join('aux_moon.moon').contents = '_G.loaded_moon = true'
dir\join('aux_bc.bc').contents = string.dump loadstring('_G.loaded_bc = true'), false
loader ->
  foo_load 'aux_lua'
  foo_load 'aux_moon'
  foo_load 'aux_bc'

assert.is_true _G.loaded_lua
assert.is_true _G.loaded_moon
assert.is_true _G.loaded_bc

prefers bytecode to Lua to Moonscript

dir\join('one.bc').contents = string.dump loadstring('return "bytecode"'), false
dir\join('one.lua').contents = 'return "lua"'

dir\join('two.lua').contents = 'return "lua"'
dir\join('two.moon').contents = 'return "moon"'

assert.equal 'bytecode', loader -> foo_load 'one'
assert.equal 'lua', loader -> foo_load 'two'

only loads each file once

dir\join('aux.lua').contents = [[
  _G.load_count = _G.load_count or 0
  _G.load_count = _G.load_count + 1
  return _G.load_count
]]
assert.equals 1, loader -> foo_load 'aux'
assert.equals 1, loader -> foo_load 'aux'

signals an error upon cyclic dependencies

dir\join('aux.lua').contents = 'foo_load("aux2")'
dir\join('aux2.lua').contents = 'foo_load("aux")'
assert.raises 'Cyclic dependency', -> loader -> foo_load 'aux'

allows passing parameters to the loaded file

dir\join('aux.lua').contents = 'return ...'
assert.equal 123, loader -> foo_load 'aux', 123

(loading files from sub directories)

supports both slashes and dots in the path

sub = dir\join('down/sub.lua')
sub.parent\mkdir!
sub.contents = 'return "sub"'

dir\join('down/sub2.lua').contents = 'return "sub2"'

assert.equals 'sub', loader -> foo_load 'down/sub'
assert.equals 'sub2', loader -> foo_load 'down.sub2'

loads the file once regardless of whether dots or slashes are used

sub = dir\join('down/sub.lua')
sub.parent\mkdir!
sub.contents = [[
  _G.load_count = _G.load_count + 1
  return _G.load_count
]]
_G.load_count = 0
assert.equals 1, loader -> foo_load 'down/sub'
assert.equals 1, loader -> foo_load 'down.sub'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/aux/scintillualexer_spec.html b/site/source/versions/0.3/doc/spec/aux/scintillualexer_spec.html deleted file mode 100644 index 2dd8b3cd7..000000000 --- a/site/source/versions/0.3/doc/spec/aux/scintillualexer_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.aux.ScintilluaLexer - - - - - - - -
-
- -
- -

howl.aux.ScintilluaLexer

calling it lexes text using the specified Scintillua lexer

tmpfile = File.tmpfile!
tmpfile.contents = [[
  local l = lexer
  local token, word_match = l.token, l.word_match
  local ws = token(l.WHITESPACE, l.space^1)
  local keyword = token(l.KEYWORD, word_match {
    'awesome',
    'stuff',
  })
  local M = { _NAME = 'spec' }
  M._rules = {
    { 'whitespace', ws },
    { 'keyword', keyword },
  }
  return M
]]
lexer = ScintilluaLexer 'spec', tmpfile
tmpfile\delete!
lexed = lexer 'awesome stuff'
assert.same {
  1, 'keyword', 8,
  8, 'whitespace', 9,
  9, 'keyword', 14
}, lexed

provides the usual pre-defined Scintillua styles in the lexer

File.with_tmpfile (file) ->
  file.contents = [[
    local new_tag = lexer.style_tag .. {}
    assert(lexer.style_class ~= nil)
    return {
      _NAME = 'futile_styling_attempt',
      _rules = { { 'any', lexer.any } }
    }
  ]]
  ScintilluaLexer 'style_craze', file

Scintillua's lexer.load()

can load other Scintillua lexers from registered modes

File.with_tmpfile (file) ->
  file.contents = [[
    return {
      _NAME = 'embedded',
      _rules = { { 'any', lexer.any } }
    }
  ]]
  lexer = ScintilluaLexer 'embedded', file
  mode.register name: 'embedded', create: -> :lexer

File.with_tmpfile (file) ->
  file.contents = [[
    local embedded = lexer.load('embedded')
    assert(embedded._RULES ~= nil, 'Failed to load sub lexer')
    return {
      _NAME = 'driver',
      _rules = { { 'any', lexer.any } }
    }
  ]]
  ScintilluaLexer 'driver', file
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/bindings_spec.html b/site/source/versions/0.3/doc/spec/bindings_spec.html deleted file mode 100644 index 5cc1b8ba7..000000000 --- a/site/source/versions/0.3/doc/spec/bindings_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.bindings - - - - - - - -
-
- -
- -

howl.bindings

after_each ->
  while #bindings.keymaps > 1
    bindings.pop!

  bindings.cancel_capture

push(map, options = {})

pushes <map> to the keymap stack at .keymaps

map = {}
bindings.push map
assert.equals map, bindings.keymaps[#bindings.keymaps]

pop()

pops the top-most keymap of the stack at .keymaps

stack_before = moon.copy bindings.keymaps
bindings.push {}
bindings.pop!
assert.same stack_before, bindings.keymaps

remove(map)

removes the specified map from the keymap stack

stack = moon.copy bindings.keymaps
m1 = {}
m2 = {}
bindings.push m1
bindings.push m2
bindings.remove m1
append stack, m2
assert.same stack, bindings.keymaps

translate_key(event)

adds special case translations for certain common keys

for_keynames = {
  kp_up: 'up'
  kp_down: 'down'
  kp_left: 'left'
  kp_right: 'right'
  kp_page_up: 'page_up'
  kp_page_down: 'page_down'
  iso_left_tab: 'tab' -- shifts are automatically prepended
  return: 'enter'
  altL: 'alt'
  altR: 'alt'
  shiftL: 'shift'
  shiftR: 'shift'
  ctrlL: 'ctrl'
  ctrlR: 'ctrl'
 }

for name, alternative in pairs for_keynames
  translations = bindings.translate_key key_code: 123, key_name: name
  assert.includes translations, alternative

substitutes certain key names to prevent ambiguity

for_keynames = {
  alt_l: 'altL'
  alt_r: 'altR'
  shift_l: 'shiftL'
  shift_r: 'shiftR'
  control_l: 'ctrlL'
  control_r: 'ctrlR'
}

for name, substitution in pairs for_keynames
  translations = bindings.translate_key key_code: 123, key_name: name
  assert.includes translations, substitution
  assert.not_includes translations, name

(for ordinary characters)

returns a table with the character, key name and key code string

tr = bindings.translate_key character: 'A', key_name: 'a', key_code: 65
assert.same tr, { 'A', 'a', '65' }

skips the translation for key name if it is the same as for character

tr = bindings.translate_key character: 'a', key_name: 'a', key_code: 65
assert.same tr, { 'a', '65' }

(when character is missing)

returns a table with key name and key code string

tr = bindings.translate_key key_name: 'down', key_code: 123
assert.same tr, { 'down', '123' }

(when only the code is available)

returns a table with the key code string

tr = bindings.translate_key key_code: 123
assert.same tr, { '123' }

(with modifiers)

prepends a modifier string representation to all translations for ctrl and alt

tr = bindings.translate_key
  character: 'A', key_name: 'a', key_code: 123,
  control: true, alt: true
mods = 'ctrl_alt_'
assert.same tr, { mods .. 'A', mods .. 'a', mods .. '123' }

emits the shift modifier if the character is known

tr = bindings.translate_key
  character: 'A', key_name: 'a', key_code: 123,
  control: true, shift: true
assert.same tr, { 'ctrl_A', 'ctrl_shift_a', 'ctrl_shift_123' }

process(event, source, extra_keymaps, ..)

(when firing the key-press signal)

passes the event, translations, source and parameters

event = character: 'A', key_name: 'a', key_code: 65

with_signal_handler 'key-press', nil, (handler) ->
  status, ret = pcall bindings.process, event, 'editor', {}, 'yowser'
  assert.spy(handler).was.called_with {
    :event
    source: 'editor'
    translations: { 'A', 'a', '65' }
    parameters: { 'yowser' }
  }

returns early with true if the handler does

keymap = A: spy.new -> true
with_signal_handler 'key-press', true, (handler) ->
  status, ret = pcall bindings.process, { character: 'A', key_name: 'A', key_code: 65 }, 'editor', { keymap }
  assert.spy(handler).was.called!
  assert.spy(keymap.A).was.not_called!
  assert.is_true ret

continues processing keymaps if the handler returns false

keymap = A: spy.new -> true
with_signal_handler 'key-press', false, (handler) ->
  status, ret = pcall bindings.process, { character: 'A', key_name: 'A', key_code: 65 }, 'editor', { keymap }
  assert.spy(keymap.A).was_called!

(when looking up handlers)

tries each translated key and .on_unhandled in order for a keymap, and optional source specific map

keymap = Spy!
bindings.process { character: 'A', key_name: 'a', key_code: 65 }, 'my_source', { keymap }
assert.same { 'my_source', 'binding_for', 'A', 'a', '65', 'on_unhandled' }, keymap.reads

prefers source specific bindings

specific_map = A: spy.new -> nil
general_map = {
  A: spy.new -> nil
  my_source: specific_map
}
bindings.process { character: 'A', key_name: 'a', key_code: 65 }, 'my_source', { general_map }
assert.spy(specific_map.A).was_called(1)
assert.spy(general_map.A).was_not_called!

searches all extra keymaps and the bindings in the stack

key_args = character: 'A', key_name: 'a', key_code: 65
extra_map = Spy!
stack_map = Spy!
bindings.push stack_map
bindings.process key_args, 'editor', { extra_map }
assert.equal 6, #stack_map.reads
assert.same stack_map.reads, extra_map.reads

(.. when .on_unhandled is defined and keys are not found in a keymap)

is called with the event, source, translations and extra parameters

keymap = on_unhandled: spy.new ->
event = character: 'A', key_name: 'a', key_code: 65
bindings.process event, 'editor', {keymap}, 'hello!'
assert.spy(keymap.on_unhandled).was.called_with(event, 'editor', { 'A', 'a', '65' }, 'hello!')

any return is used as the handler

handler = spy.new ->
keymap = on_unhandled: -> handler
bindings.process { character: 'A', key_name: 'A', key_code: 65 }, 'editor', { keymap }
assert.spy(handler).was.called!

(.. when .binding_for is defined and keys are not found in a keymap)

is looked up by keys bound to the commands in .binding_for

handler = spy.new ->
bindings.push a: 'my-command'
bindings.push binding_for: ['my-command']: handler
bindings.process {character: 'a', key_name: 'a', key_code: 97}, ''
assert.spy(handler).was.called!

(.. when a keymap was pushed with options.block set to true)

looks no further down the stack than that keymap

base = k: spy.new -> nil
blocking = {}
bindings.push base
bindings.push blocking, block: true
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.spy(base.k).was_not_called!

(.. when a keymap was pushed with options.pop set to true)

is automatically popped after the next dispatch

pop_me = k: spy.new -> nil
bindings.push pop_me, pop: true
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.spy(pop_me.k).was_called!
assert.not_includes bindings.keymaps, pop_me

is popped regardless of whether it contained a matching binding or not

pop_me = {}
bindings.push pop_me, pop: true
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.not_includes bindings.keymaps, pop_me

is always blocking

base = k: spy.new -> nil
pop_me = k: spy.new -> nil
bindings.push base
bindings.push pop_me, pop: true
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.spy(pop_me.k).was_called!
assert.spy(base.k).was_not_called!

(when invoking handlers)

invokes handlers in their own coroutines

coros = {}
coro_register = ->
  co, main = coroutine.running!
  coros[co] = true unless main

keymap = k: coro_register
for i = 1,2
  bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }

assert.equal 2, #[v for _, v in pairs coros]

returns false if no handlers are found

assert.is_false bindings.process { character: 'k', key_code: 65 }, 'editor'

invokes handlers in extra keymaps before the default keymap

bindings.keymap = k: spy.new -> nil
extra_map = k: spy.new -> nil
bindings.process { character: 'k', key_code: 65 }, 'editor', { extra_map }
assert.spy(extra_map.k).was_called(1)
assert.spy(bindings.keymap.k).was_not_called!

(.. when the handler is callable)

passes along any extra arguments

keymap = k: spy.new ->
bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }, 'reference'
assert.spy(keymap.k).was.called_with('reference')

returns early with true unless a handler explicitly returns false

first = k: spy.new ->
second = k: spy.new ->
assert.is_true bindings.process { character: 'k', key_code: 65 }, 'space', { first, second }
assert.spy(second.k).was.not_called!

(.. when the handler raises an error)

returns true

keymap = { k: -> error 'BOOM!' }
assert.is_true bindings.process { character: 'k', key_code: 65 }, 'mybad', { keymap }

logs an error to the log

keymap = { k: -> error 'a to the k log' }
bindings.process { character: 'k', key_code: 65 }, 'mybad', { keymap }
assert.is_not.equal #log.entries, 0
assert.equal log.entries[#log.entries].message, 'a to the k log'

(.. when the handler is a string)

runs the command with command.run() and returns true

cmd_run = spy.on(command, 'run')
keymap = k: 'spy'
assert.is_true bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }
command.run\revert!
assert.spy(cmd_run).was.called_with 'spy'

(.. when the handler is a non-callable table)

pushes the table as a new keymap and returns true

nr_bindings = #bindings.keymaps
submap = {}
keymap = k: submap
assert.is_true bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }
assert.equal nr_bindings + 1, #bindings.keymaps
assert.equal submap, bindings.keymaps[#bindings.keymaps]

pushes the table with the pop option

submap = {}
keymap = k: submap
bindings.process { character: 'k', key_code: 65 }, 'editor', { keymap }
bindings.process { character: 'k', key_code: 65 }, 'editor'
assert.not_includes bindings.keymaps, submap

capture(function)

causes <function> to be called exclusively with event, source, translations and any extra parameters

event = character: 'A', key_name: 'a', key_code: 65
thief = spy.new -> true
keymap = A: spy.new -> true
bindings.capture thief
bindings.process event, 'source', { keymap }, 'catch-me!'
assert.spy(keymap.A).was_not.called!
assert.spy(thief).was.called_with(event, 'source', { 'A', 'a', '65' }, 'catch-me!')

<function> continues to capture events as long as it returns false

ret = false
event = character: 'A', key_name: 'A', key_code: 65
thief = spy.new -> return ret
bindings.capture thief
bindings.process event, 'editor'
ret = nil
bindings.process event, 'editor'
bindings.process event, 'editor'
assert.spy(thief).was.called(2)

cancel_capture()

cancels any currently set capture

thief = spy.new -> return ret
  bindings.capture thief
  bindings.cancel_capture!
  bindings.process { character: 'A', key_name: 'A', key_code: 65 }, 'editor'
  assert.spy(thief).was_not.called!

.is_capturing

is true when a capture is active and false otherwise

assert.is_false bindings.is_capturing
bindings.capture -> nil
assert.is_true bindings.is_capturing
bindings.process { character: 'A', key_name: 'A', key_code: 65 }, 'editor'
assert.is_false bindings.is_capturing

keystrokes_for(handler, source)

returns all the key bindings that maps to <handler>

bindings.push ctrl_y: 'my-command'
bindings.push ctrl_x: 'my-command'
assert.same {'ctrl_x', 'ctrl_y'}, bindings.keystrokes_for 'my-command'

returns an empty table if no binding was found

assert.same {}, bindings.keystrokes_for 'my-command'

action_for(translation)

local saved_keymaps

before_each ->
  saved_keymaps = bindings.keymaps
  bindings.keymaps = {}

after_each ->
  bindings.keymaps = saved_keymaps

returns the command bound to translation

bindings.push ctrl_x: 'my-old-command'
bindings.push ctrl_x: 'my-new-command'
assert.equals 'my-new-command', bindings.action_for 'ctrl_x'

returns nil if no command was found

assert.is_nil bindings.action_for 'ctrl_x'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/buffer_context_spec.html b/site/source/versions/0.3/doc/spec/buffer_context_spec.html deleted file mode 100644 index 41cfdfa92..000000000 --- a/site/source/versions/0.3/doc/spec/buffer_context_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.BufferContext - - - - - - - -
-
- -
- -

howl.BufferContext

local b

before_each ->
  b = Buffer!
  b.text = '"HƏllo", said Mr.Bačon'

context_at = (pos) -> BufferContext b, pos

.word_prefix holds the words's text up until pos

assert.equal '', context_at(2).word_prefix
assert.equal 'HƏ', context_at(4).word_prefix

.word_suffix holds the words's text after and including pos

assert.equal 'HƏllo', context_at(2).word_suffix
assert.equal 'llo', context_at(4).word_suffix

.prefix holds the line's text up until pos

assert.equal '', context_at(1).prefix
assert.equal '"HƏllo", said Mr.Bačon', context_at(#b + 1).prefix
assert.equal '"H', context_at(3).prefix

.suffix holds the line's text after and including pos

assert.equal '', context_at(#b + 1).suffix
assert.equal '"HƏllo", said Mr.Bačon', context_at(1).suffix
assert.equal 'Mr.Bačon', context_at(15).suffix

.next_char holds the current character or the empty string if none

assert.equal 'Ə', context_at(3).next_char
assert.equal '', context_at(#b + 1).next_char

.prev_char holds the previous character or the empty string if none

assert.equal 'Ə', context_at(4).prev_char
assert.equal '', context_at(1).prev_char

.line holds the current line object

assert.equal b.lines[1], context_at(1).line

.style holds the style at point

buf = ActionBuffer!
buf\append '[', 'operator'
buf\append '"foo"', 'string'
buf\append ' normal'
assert.equal 'operator', BufferContext(buf, 1).style
assert.equal 'string', BufferContext(buf, 2).style
assert.equal 'unstyled', BufferContext(buf, 7).style

contexts are equal for the same buffer and pos

assert.equal context_at(2), context_at(2)
assert.not_equal context_at(2), context_at(4)

.word

holds the current word

assert.equal '', context_at(1).word.text
assert.equal 'HƏllo', context_at(2).word.text
assert.equal 'HƏllo', context_at(4).word.text
assert.equal 'HƏllo', context_at(6).word.text
assert.equal '', context_at(8).word.text
assert.equal '', context_at(9).word.text
assert.equal 'said', context_at(14).word.text
assert.equal 'Mr', context_at(16).word.text
assert.equal 'Bačon', context_at(19).word.text

b.text = 'first'
assert.equal 'first', context_at(1).word.text

the word boundaries are determined using the variable word_pattern

b.config.word_pattern = '[Əl]+'
assert.equal 'Əll', context_at(3).word.text

b.config.word_pattern = '["Ə%w]+'
assert.equal '"HƏllo"', context_at(3).word.text
assert.equal '"HƏllo"', context_at(8).word.text -- after "
assert.equal '', context_at(9).word.text -- after ','

the word_pattern can be a regex

b.config.word_pattern = r'\\pL+'
assert.equal 'HƏllo', context_at(3).word.text
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/buffer_lines_spec.html b/site/source/versions/0.3/doc/spec/buffer_lines_spec.html deleted file mode 100644 index 0c3128fc6..000000000 --- a/site/source/versions/0.3/doc/spec/buffer_lines_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.BufferLines - - - - - - - -
-
- -
- -

howl.BufferLines

buffer = (text) ->
  with Buffer {}
    .text = text

# operator returns number of lines in the buffer

b = buffer 'hello\n  world\nagain!'
assert.equal #b.lines, 3

delete(start, end) deletes the the lines [start, end]

b = buffer 'hellØ\nwØrld\nagain!'
  b.lines\delete 1, 2
  assert.equal b.text, 'again!'

at_pos(pos) returns the line at <pos>

lines = buffer('Øne\ntwØ\nthree').lines
line = lines\at_pos 5
assert.equal 'twØ', line.text

supports iterating using ipairs

b = buffer 'one\ntwo\nthree'
for i, line in ipairs b.lines
  assert.equal line, b.lines[i]

supports iterating using pairs

b = buffer 'one\ntwo\nthree'
for i, line in pairs b.lines
  assert.equal line, b.lines[i]

Line objects

buf = nil
lines = nil

before_each ->
  buf = buffer 'hƏllØ\n  wØrld\nagain!'
  lines = buf.lines

.buffer points to the corresponding buffer

assert.same buf, lines[1].buffer

.nr holds the line number

assert.equal lines[1].nr, 1

.text returns the text of the specified line, sans linebreak

assert.equal lines[1].text, 'hƏllØ'
assert.equal lines[2].text, '  wØrld'
assert.equal lines[3].text, 'again!'

tostring(line) gives the same as .text

assert.equal tostring(lines[1]), lines[1].text

.indentation returns the indentation for the line

assert.equal lines[1].indentation, 0
assert.equal lines[2].indentation, 2

.indentation = <nr> set the indentation for the line to <nr>

lines[3].indentation = 4
assert.equal 'hƏllØ\n  wØrld\n    again!', buf.text

.start_pos returns the start position for line

assert.equal lines[2].start_pos, 7
buf.text = ''
assert.equal lines[1].start_pos, 1

.end_pos returns the end position for line, right before the newline

assert.equal lines[1].end_pos, 6
buf.text = ''
assert.equal lines[1].end_pos, 1

.byte_start_pos returns the byte start position for line

buf.text = 'åäö\nwØrld'
assert.equal lines[2].byte_start_pos, 8
buf.text = ''
assert.equal lines[1].byte_start_pos, 1

.byte_end_pos returns the byte end position for line

buf.text = 'åäö\nwØrld'
assert.equal lines[1].byte_end_pos, 7
buf.text = ''
assert.equal lines[1].byte_end_pos, 1

.previous returns the line above this one, or nil if none

assert.equal lines[2].previous, lines[1]
assert.is_nil lines[1].previous

.previous_non_blank returns the first preceding non-blank line, or nil if none

assert.is_nil lines[1].previous_non_blank
assert.equal lines[1], lines[2].previous_non_blank
lines\insert 3, ''
assert.equal lines[2], lines[4].previous_non_blank

.next_non_blank returns the first succeding non-blank line, or nil if none

assert.is_nil lines[3].next_non_blank
assert.equal lines[3], lines[2].next_non_blank
lines\insert 3, ''
assert.equal lines[4], lines[2].next_non_blank

.next returns the line below this one, or nil if none

assert.equal lines[1].next, lines[2]
assert.is_nil lines[3].next

.indent() indents the line by <config.indent>

config.indent = 2
buf.lines[1]\indent!
assert.equal '  hƏllØ\n  wØrld\nagain!', buf.text

buf.config.indent = 1
buf.lines[3]\indent!
assert.equal '  hƏllØ\n  wØrld\n again!', buf.text

.unindent() unindents the line by <config.indent>

buf.text = '  first\n  second'
config.indent = 2
buf.lines[1]\unindent!
assert.equal buf.text, 'first\n  second'

buf.config.indent = 1
buf.lines[2]\unindent!
assert.equal buf.text, 'first\n second'

#line returns the length of the line

assert.equal #lines[1], 5

lines are equal if they have the same text

lines[2] = 'hƏllØ'
assert.equal lines[1], lines[2]

string methods can be accessed directly on the object

buf.text = 'first line'
line = lines[1]
assert.equal 'fi', line\sub(1,2)
assert.equal 8, (line\find('in'))
assert.equal 'first win', (line\gsub('line', 'win'))

string properties can be accessed directly on the object

assert.is_false lines[1].is_empty
assert.is_false lines[1].is_blank
lines[1] = ''
assert.is_true lines[1].is_empty
assert.is_true lines[1].is_blank

.text = <content>

replaces the line text with <content>

lines[1].text = 'Hola'
assert.equal buf.text, 'Hola\n  wØrld\nagain!'

raises an error if <content> is nil

assert.raises 'nil', -> lines[1].text = nil

.chunk

a Chunk object for the line, disregarding the newline

buf.text = 'hƏllØ\nbare'
chunk = lines[1].chunk
assert.equal 'Chunk', typeof chunk
assert.equal 'hƏllØ', chunk.text
assert.equal 1, chunk.start_pos
assert.equal 5, chunk.end_pos

chunk = lines[2].chunk
assert.equal 'bare', chunk.text
assert.equal 7, chunk.start_pos
assert.equal 10, chunk.end_pos

is an empty chunk for empty lines

buf.text = '\n'
chunk = lines[1].chunk
assert.equal '', chunk.text
assert.equal 1, chunk.start_pos
assert.equal 0, chunk.end_pos

chunk = lines[2].chunk
assert.equal '', chunk.text
assert.equal 2, chunk.start_pos
assert.equal 1, chunk.end_pos

[nr]

returns a line object for the specified line

lines = buffer('hello\n  world\nagain!').lines
assert.equal lines[1].text, 'hello'

returns nil if the line number is invalid

lines = buffer('hello!').lines
assert.is_nil lines[2]
assert.is_nil lines[0]

[nr] = <value>

replaces the text of the specified line with <value>

b = buffer 'hellØ\nwØrld'
b.lines[1] = 'hØla'
assert.equal b.text, 'hØla\nwØrld'

removes the entire line if value is nil

b = buffer 'gØØdbye\ncruel\nwØrld'
b.lines[2] = nil
assert.equal 'gØØdbye\nwØrld', b.text
b.lines[1] = nil
assert.equal 'wØrld' ,b.text

raises an error if the line number is invalid

b = buffer 'hello!'
assert.raises 'Invalid index', -> b.lines['foo'] = 'bar'

range(start, end)

returns a table with lines [start, end]

lines = buffer('one\ntwo\nthree').lines
range = lines\range 1, 2
assert.same { lines[1], lines[2] }, range

start can be greater than end

lines = buffer('one\ntwo\nthree').lines
range = lines\range 2, 1
assert.same { lines[1], lines[2] }, range

for_text_range(start_pos, end_pos)

returns a table with lines between [start_pos, end_pos]

lines = buffer('one\ntwo\nthree').lines
range = lines\for_text_range 2, 6
assert.same { lines[1], lines[2] }, range

start_pos can be greater than end_pos

lines = buffer('one\ntwo\nthree').lines
range = lines\for_text_range 6, 1
assert.same { lines[1], lines[2] }, range

does not include lines only touched at the start or end positions

lines = buffer('one\ntwo\nthree').lines
range = lines\for_text_range lines[1].end_pos, lines[3].start_pos
assert.same { lines[2] }, range

append(text)

append(text) appends <text> with the necessary newlines

b = buffer 'one\ntwo'
b.lines\append 'three'
assert.equal b.text, 'one\ntwo\nthree\n'

b = buffer 'one\ntwo\n'
b.lines\append 'three'
assert.equal b.text, 'one\ntwo\nthree\n'

returns a line object for the newly appended line

b = buffer 'line'
line = b.lines\append 'omega'
assert.equal line, b.lines[2]

insert(line_nr, text)

inserts a new line at <nr> with <text>

b = buffer 'one\ntwo'
b.lines\insert 1, 'half'
assert.equal b.text, 'half\none\ntwo'

b.lines\insert 3, '1.5'
assert.equal b.text, 'half\none\n1.5\ntwo'

appends the line if line_nr is just beyond the last line

b = buffer 'first\nsecond'
b.lines\insert 3, 'foo'
assert.equal b.text, 'first\nsecond\nfoo\n'

raises an error if <line_nr> is invalid

b = buffer 'first\nsecond'
assert.raises 'Invalid', -> b.lines\insert 0, 'foo'
assert.raises 'Invalid', -> b.lines\insert 4, 'foo'

returns a line object for the newly inserted line

b = buffer 'line'
line = b.lines\insert 1, 'alpha'
assert.equal line, b.lines[1]
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/buffer_spec.html b/site/source/versions/0.3/doc/spec/buffer_spec.html deleted file mode 100644 index db039adcc..000000000 --- a/site/source/versions/0.3/doc/spec/buffer_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Buffer - - - - - - - -
-
- -
- -

howl.Buffer

local sci

before_each ->
  sci = Scintilla!

buffer = (text) ->
  with Buffer {}
    .text = text

.text allows setting and retrieving the buffer text

b = Buffer {}
assert.equal b.text, ''
b.text = 'Ipsum'
assert.equal 'Ipsum', b.text

.size returns the size of the buffer text, in bytes

assert.equal buffer('hello').size, 5
assert.equal buffer('åäö').size, 6

.length returns the size of the buffer text, in characters

assert.equal 5, buffer('hello').length
assert.equal 3, buffer('åäö').length

.modified indicates and allows setting the modified status

b = Buffer {}
assert.is_false b.modified
b.text = 'hello'
assert.is_true b.modified
b.modified = false
assert.is_false b.modified
b.modified = true
assert.is_true b.modified
assert.equal b.text, 'hello' -- toggling should not have changed text

.read_only can be set to mark the buffer as read-only

b = Buffer!
b.read_only = true
assert.equal true, b.read_only
b\append 'illegal'
assert.equal '', b.text
b.read_only = false
b\append 'yes'
assert.equal 'yes', b.text

.eol returns the current line ending

b = buffer ''

b.sci\set_eolmode Scintilla.SC_EOL_CRLF
assert.equal b.eol, '\r\n'

b.sci\set_eolmode Scintilla.SC_EOL_LF
assert.equal b.eol, '\n'

b.sci\set_eolmode Scintilla.SC_EOL_CR
assert.equal b.eol, '\r'

.properties is a table

assert.equal 'table', type buffer('').properties

.data is a table

assert.equal 'table', type buffer('').data

.showing is true if the buffer is currently referenced in any sci

b = buffer ''
assert.false b.showing
b\add_sci_ref sci
assert.true b.showing

.destroyed is true if the buffer is destroyed and false otherwise

b = buffer 'shoot_me'
assert.is_false b.destroyed
b\destroy!
assert.is_true b.destroyed

undo undoes the last operation

b = buffer 'hello'
b\delete 1, 1
b\undo!
assert.equal b.text, 'hello'

.can_undo returns true if undo is possible, and false otherwise

b = Buffer {}
assert.is_false b.can_undo
b.text = 'bar'
assert.is_true b.can_undo
b\undo!
assert.is_false b.can_undo

#buffer returns the number of characters in the buffer

assert.equal 5, #buffer('hello')
assert.equal 3, #buffer('åäö')

tostring(buffer) returns the buffer title

b = buffer 'hello'
b.title = 'foo'
assert.equal tostring(b), 'foo'

creation

(when sci parameter is specified)

attaches .sci and .doc to the Scintilla instance

sci.get_doc_pointer = -> 'docky'
b = Buffer {}, sci
assert.equal b.doc, 'docky'
assert.equal b.sci, sci

.mode = <mode>

(when <mode> has a lexer)

updates all embedding scis to use container lexing

b = Buffer!
b\add_sci_ref sci
assert.equal Scintilla.SCLEX_NULL, sci\get_lexer!
b.mode = lexer: -> {}
assert.equal Scintilla.SCLEX_CONTAINER, sci\get_lexer!

(when <mode> does not have a lexer)

updates all embedding scis to use null lexing

b = Buffer lexer: -> {}
b\add_sci_ref sci
assert.equal Scintilla.SCLEX_CONTAINER, sci\get_lexer!
b.mode = {}
assert.equal Scintilla.SCLEX_NULL, sci\get_lexer!

.file = <file>

b = buffer ''

sets the title to the basename of the file

with_tmpfile (file) ->
  b.file = file
  assert.equal b.title, file.basename

marks the buffer as not modified

with_tmpfile (file) ->
  b.file = file
  assert.is_false b.modified

clears the undo history

with_tmpfile (file) ->
  b.file = file
  assert.is_false b.can_undo

when <file> exists

overwrites any existing buffer text even if the buffer is modified

b.text = 'foo'
with_tmpfile (file) ->
  file.contents = 'yes sir'
  b.file = file
  assert.equal b.text, 'yes sir'

and the buffer is not modified

before_each ->
  b.text = 'foo'
  b.modified = false

sets the buffer text to the contents of the file

with_tmpfile (file) ->
  file.contents = 'yes sir'
  b.file = file
  assert.equal b.text, 'yes sir'

when <file> does not exist

set the buffer text to the empty string

b.text = 'foo'
with_tmpfile (file) ->
  file\delete!
  b.file = file
  assert.equal '', b.text

.eol = <string>

set the the current line ending

b = buffer ''

b.eol = '\n'
assert.equal b.sci\get_eolmode!, Scintilla.SC_EOL_LF

b.eol = '\r\n'
assert.equal b.sci\get_eolmode!, Scintilla.SC_EOL_CRLF

b.eol = '\r'
assert.equal b.sci\get_eolmode!, Scintilla.SC_EOL_CR

raises an error if the eol is unknown

assert.raises 'Unknown', -> buffer('').eol = 'foo'

.multibyte

returns true if the buffer contains multibyte characters

assert.is_false buffer('vanilla').multibyte
assert.is_true buffer('HƏllo').multibyte

is updated whenever text is inserted

b = buffer 'vanilla'
b\append 'Bačon'
assert.is_true b.multibyte

is unset whenever a previously multibyte buffer has its length calculated

b = buffer('HƏllo')
b\delete 2, 2
b.length
assert.is_false b.multibyte

.modified_on_disk

is false for a buffer with no file

assert.is_false Buffer!.modified_on_disk

is true if the file's etag is changed after a load or save

file = contents: 'foo', etag: '1', basename: 'changeable', exists: true
b = Buffer!
b.file = file
file.etag = '2'
assert.is_true b.modified_on_disk
b\save!
assert.is_false b.modified_on_disk

.config

config.define name: 'buf_var', description: 'some var', default: 'def value'

allows reading and writing (local) variables

b = buffer 'config'
assert.equal 'def value', b.config.buf_var
b.config.buf_var = 123
assert.equal 123, b.config.buf_var
assert.equal 'def value', config.buf_var

is chained to the mode config when available

mode_config = config.local_proxy!
mode = config: mode_config
b = buffer 'config'
b.mode = mode
mode_config.buf_var = 'from_mode'
assert.equal 'from_mode', b.config.buf_var

is chained to the global config when mode config is not available

b = buffer 'config'
b.mode = {}
assert.equal 'def value', b.config.buf_var

delete(start_pos, end_pos)

deletes the specified range, inclusive

b = buffer 'ño örf'
b\delete 2, 4
assert.equal 'ñrf', b.text

does nothing if end_pos is smaller than start_pos

b = buffer 'hello'
b\delete 2, 1
assert.equal 'hello', b.text

insert(text, pos)

inserts text at pos

b = buffer 'ño señor'
b\insert 'me gusta ', 4
assert.equal 'ño me gusta señor', b.text

returns the position right after the inserted text

b = buffer ''
assert.equal 6, b\insert 'Bačon', 1

append(text)

appends the specified text

b = buffer 'hello'
b\append ' world'
assert.equal b.text, 'hello world'

returns the position right after the inserted text

b = buffer ''
assert.equal 6, b\append 'Bačon'

replace(pattern, replacement)

replaces all occurences of pattern with replacement

b = buffer 'hello\nuñi©ode\nworld\n'
b\replace '[lo]', ''
assert.equal 'he\nuñi©de\nwrd\n', b.text

returns the number of occurences replaced

b = buffer 'hello\nworld\n'
assert.equal 1, b\replace('world', 'editor')

(when pattern contains a leading grouping)

replaces only the match within pattern with replacement

b = buffer 'hello\nworld\n'
b\replace '(hel)lo', ''
assert.equal 'lo\nworld\n', b.text

destroy()

raises an error if the buffer is currently showing

b = buffer 'not yet'
b\add_sci_ref sci
assert.raises 'showing', -> b\destroy!

a destroyed buffer raises an error upon subsequent operations

b = buffer 'reap_me'
b\destroy!
assert.raises 'destroyed', -> b.size
assert.raises 'destroyed', -> b.lines
assert.raises 'destroyed', -> b\append 'foo'

(when no sci is passed and a doc is created in the constructor)

releases the scintilla document

b = buffer 'reap_me'
rawset b, 'sci', Spy as_null_object: true
b\destroy!
assert.is_true b.sci.release_document.called

(when a sci is passed and a doc is provided in the constructor)

an error is raised since the buffer is considered as currently showing

sci.get_doc_pointer = -> 'doc'
sci.release_document = spy.new -> nil
b = Buffer {}, sci
assert.raises 'showing', -> b\destroy!
assert.spy(sci.release_document).was_not.called!

.can_undo = <bool>

setting it to false removes any undo history

b = buffer 'hello'
assert.is_true b.can_undo
b.can_undo = false
assert.is_false b.can_undo
b\undo!
assert.equal b.text, 'hello'

setting it to true is a no-op

b = buffer 'hello'
assert.is_true b.can_undo
b.can_undo = true
assert.is_true b.can_undo
b\undo!
b.can_undo = true
assert.is_false b.can_undo

as_one_undo(f)

allows for grouping actions as one undo

b = buffer 'hello'
b\as_one_undo ->
  b\delete 1, 1
  b\append 'foo'
b\undo!
assert.equal b.text, 'hello'

(when f raises an error)

propagates the error

b = buffer 'hello'
assert.raises 'oh my',  ->
  b\as_one_undo -> error 'oh my'

ends the undo transaction

b = buffer 'hello'
assert.error -> b\as_one_undo ->
  b\delete 1, 1
  error 'oh noes what happened?!?'
b\append 'foo'
b\undo!
assert.equal b.text, 'ello'

save()

(when a file is assigned)

stores the contents of the buffer in the assigned file

text = 'line1\nline2♥\nåäö\n'
b = buffer text
with_tmpfile (file) ->
  b.file = file
  b.text = text
  b\save!
  assert.equal text, file.contents

clears the modified flag

with_tmpfile (file) ->
  b = buffer 'foo'
  b.file = file
  b\append ' bar'
  assert.is_true b.modified
  b\save!
  assert.is_false b.modified

(.. when config.strip_trailing_whitespace is false)

does not strip trailing whitespace before saving

with_tmpfile (file) ->
  config.strip_trailing_whitespace = false
  b = buffer ''
  b.file = file
  b.text = 'blank  \n\nfoo \n'
  b\save!
  assert.equal 'blank  \n\nfoo \n', b.text
  assert.equal file.contents, b.text

(.. when config.strip_trailing_whitespace is true)

strips trailing whitespace at the end of lines before saving

with_tmpfile (file) ->
  config.strip_trailing_whitespace = true
  b = buffer ''
  b.file = file
  b.text = 'åäö  \n\nfoo  \n  '
  b\save!
  assert.equal 'åäö\n\nfoo\n', b.text
  assert.equal file.contents, b.text

(.. when config.ensure_newline_at_eof is true)

appends a newline if necessary

with_tmpfile (file) ->
  config.ensure_newline_at_eof = true
  b = buffer ''
  b.file = file
  b.text = 'look mah no newline!'
  b\save!
  assert.equal 'look mah no newline!\n', b.text
  assert.equal file.contents, b.text

(.. when config.ensure_newline_at_eof is false)

does not appends a newline

with_tmpfile (file) ->
  config.ensure_newline_at_eof = false
  b = buffer ''
  b.file = file
  b.text = 'look mah no newline!'
  b\save!
  assert.equal 'look mah no newline!', b.text
  assert.equal file.contents, b.text

save_as(file)

associates the buffer with <file> henceforth

with_tmpfile (file) ->
  file.contents = 'orig'
  b = buffer ''
  b.file = file
  with_tmpfile (new_file) ->
    b.text = 'nuevo'
    b\save_as new_file
    assert.equal 'nuevo', new_file.contents
    assert.equal new_file, b.file

(when <file> does not exist)

saves the buffer content in the newly created file

with_tmpfile (file) ->
  file\delete!
  b = buffer 'new'
  b\save_as file
  assert.equal 'new', file.contents

(when <file> exists)

overwrites any previous content with the buffer contents

with_tmpfile (file) ->
  file.contents = 'old'
  b = buffer 'new'
  b\save_as file
  assert.equal 'new', file.contents

byte_offset(char_offset)

returns the byte offset for the given <char_offset>

b = buffer 'äåö'
for p in *{
  {1, 1},
  {3, 2},
  {5, 3},
  {7, 4},
}
  assert.equal p[1], b\byte_offset p[2]

raises an error for an out-of-bounds <char_offset>

assert.has_error -> buffer'äåö'\byte_offset 5
assert.has_error -> buffer'äåö'\byte_offset 0
assert.has_error -> buffer'a'\byte_offset -1

char_offset(byte_offset)

returns the character offset for the given <byte_offset>

b = buffer 'äåö'
for p in *{
  {1, 1},
  {3, 2},
  {5, 3},
  {7, 4},
}
  assert.equal p[2], b\char_offset p[1]

raises error for out-of-bounds offsets

assert.has_error -> buffer'ab'\char_offset 4
assert.has_error -> buffer'äåö'\char_offset 0
assert.has_error -> buffer'a'\char_offset -1

sub(start_pos, end_pos)

returns the text between start_pos and end_pos, both inclusive

b = buffer 'hållö\nhållö\n'
assert.equal 'h', b\sub(1, 1)
assert.equal 'å', b\sub(2, 2)
assert.equal 'hållö', b\sub(1, 5)
assert.equal 'hållö\nhållö\n', b\sub(1, 12)
assert.equal 'ållö', b\sub(8, 11)
assert.equal '\n', b\sub(12, 12)
assert.equal '\n', b\sub(12, 13)

handles negative indices by counting from end

b = buffer 'hållö\nhållö\n'
assert.equal '\n', b\sub(-1, -1)
assert.equal 'hållö\n', b\sub(-6, -1)
assert.equal 'hållö\nhållö\n', b\sub(-12, -1)

returns empty string for start_pos > end_pos

b = buffer 'abc'
assert.equal '', b\sub(2, 1)

handles out-of-bounds offsets gracefully

assert.equals '', buffer'abc'\sub 4, 6
assert.equals 'abc', buffer'abc'\sub 1, 6

find(pattern [, init ])

searches forward

b = buffer 'ä öx'
assert.same { 1, 4 }, { b\find 'ä öx' }
assert.same { 2, 3 }, { b\find ' ö' }
assert.same { 3, 4 }, { b\find 'öx' }
assert.same { 4, 4 }, { b\find 'x' }

searches forward from init when specified

b = buffer 'öåååö'
assert.same { 2, 3 }, { b\find 'åå', 2 }
assert.same { 3, 4 }, { b\find 'åå', 3 }
assert.is_nil b\find('åå', 4)

negative init specifies offset from end

b = buffer 'öååååö'
assert.same { 4, 5 }, { b\find 'åå', -3 }
assert.same { 2, 3 }, { b\find 'åå', -5 }
assert.is_nil b\find('åå', -2)

returns nil for out of bounds init

b = buffer 'abcde'
assert.is_nil b\find('a', -6)
assert.is_nil b\find('a', 6)

rfind(pattern [, init ])

searches backward from end

b = buffer 'äöxöx'
assert.same { 1, 3 }, { b\rfind 'äöx' }
assert.same { 4, 5 }, { b\rfind 'öx' }
assert.same { 5, 5 }, { b\rfind 'x' }

searches backward from init when specified

b = buffer 'öååååö'
assert.same { 4, 5 }, { b\rfind 'åå', 5 }
assert.same { 3, 4 }, { b\rfind 'åå', 4 }
assert.is_nil b\rfind('åå', 2)

negative init specifies offset from end

b = buffer 'öååååö'
assert.same { 4, 5 }, { b\rfind 'åå', -2 }
assert.same { 2, 3 }, { b\rfind 'åå', -4 }
assert.is_nil b\rfind('åå', -5)

returns nil for out of bounds init

b = buffer 'abcde'
assert.is_nil b\rfind('a', -6)
assert.is_nil b\rfind('a', 6)

reload(force = false)

reloads the buffer contents from file and returns true

with_tmpfile (file) ->
  b = buffer ''
  file.contents = 'hello'
  b.file = file
  file.contents = 'there'
  assert.is_true b\reload!
  assert.equal 'there', b.text

raises an error if the buffer is not associated with a file

assert.raises 'file', -> Buffer!\reload!

(when the buffer is modified)

leaves the buffer alone and returns false

with_tmpfile (file) ->
  b = buffer ''
  file.contents = 'hello'
  b.file = file
  b\append ' world'
  file.contents = 'there'
  assert.is_false b\reload!
  assert.equal 'hello world', b.text

specifying <force> as true reloads the buffer anyway

with_tmpfile (file) ->
  b = buffer ''
  file.contents = 'hello'
  b.file = file
  b\append ' world'
  file.contents = 'there'
  assert.is_true b\reload true
  assert.equal 'there', b.text

.add_sci_ref(sci)

adds the specified sci to .scis

b = buffer ''
b\add_sci_ref sci
assert.same b.scis, { sci }

sets .sci to the specified sci

b = buffer ''
b\add_sci_ref sci
assert.equal b.sci, sci

sets the sci lexer to container if the mode has a lexer

b = buffer ''
b.mode.lexer = -> {}
sci\set_lexer Scintilla.SCLEX_NULL
b\add_sci_ref sci
assert.equal Scintilla.SCLEX_CONTAINER, sci\get_lexer!

sets the sci lexer to null if mode has no lexer

b = buffer ''
sci\set_lexer Scintilla.SCLEX_CONTAINER
b\add_sci_ref sci
assert.equal Scintilla.SCLEX_NULL, sci\get_lexer!

.remove_sci_ref(sci)

removes the specified sci from .scis

b = buffer ''
b\add_sci_ref sci
b\remove_sci_ref sci
assert.same b.scis, {}

sets .sci to some other sci if they were previously the same

sci2 = Scintilla!
b = buffer ''
b\add_sci_ref sci
b\add_sci_ref sci2
assert.equal b.sci, sci2
b\remove_sci_ref sci2
assert.equal b.sci, sci

ensuring that buffer titles are globally unique

(when setting a file for a buffer)

prepends to the title as many parent directories as needed for uniqueness

b1 = Buffer {}
b2 = Buffer {}
b3 = Buffer {}
with_tmpdir (dir) ->
  sub1 = dir\join('sub1')
  sub1\mkdir!
  sub2 = dir\join('sub2')
  sub2\mkdir!
  f1 = sub1\join('file.foo')
  f2 = sub2\join('file.foo')
  f1\touch!
  f2\touch!
  b1.file = f1
  b2.file = f2
  assert.equal b2.title, 'sub2' .. File.separator .. 'file.foo'

  sub_sub = sub1\join('sub2')
  sub_sub\mkdir!
  f3 = sub_sub\join('file.foo')
  f3\touch!
  b3.file = f3
  assert.equal b3.title, 'sub1' .. File.separator .. b2.title

does not unneccesarily transform the title when setting the same file for a buffer

b = Buffer!
with_tmpfile (file) ->
  b.file = file
  title = b.title
  b.file = file
  assert.equal title, b.title

(when setting the title explicitly)

appends a counter number in the format <number> to the title

b1 = Buffer {}
b2 = Buffer {}
b1.title = 'Title'
b2.title = 'Title'
assert.equal b2.title, 'Title<2>'

resource management

scintilla documents are released whenever the buffer is garbage collected

release = Spy!
orig_release = Scintilla.release_document
Scintilla.release_document = release
b = Buffer {}
doc = b.doc
b = nil
collectgarbage!
Scintilla.release_document = orig_release
assert.equal release.called_with[2], doc

buffers are collected as they should

b = Buffer {}
bufs = setmetatable {}, __mode: 'v'
append bufs, b
b = nil
collectgarbage!
assert.is_nil bufs[1]

signals

buffer-saved is fired whenever a buffer is saved

with_signal_handler 'buffer-saved', nil, (handler) ->
  b = buffer 'foo'
  with_tmpfile (file) ->
    b.file = file
    b\save!

  assert.spy(handler).was_called!

text-inserted is fired whenever text is inserted into a buffer

with_signal_handler 'text-inserted', nil, (handler) ->
  b = buffer 'foo'
  b\append 'bar'
  assert.spy(handler).was_called!

text-deleted is fired whenever text is deleted from buffer

with_signal_handler 'text-inserted', nil, (handler) ->
  b = buffer 'foo'
  b\delete 1, 2
  assert.spy(handler).was_called!

buffer-modified is fired whenever a buffer is modified

with_signal_handler 'buffer-modified', nil, (handler) ->
  b = buffer 'foo'
  b\append 'bar'
  assert.spy(handler).was_called!

buffer-reloaded is fired whenever a buffer is reloaded

with_signal_handler 'buffer-reloaded', nil, (handler) ->
  with_tmpfile (file) ->
    b = buffer 'foo'
    b.file = file
    b\reload!
    assert.spy(handler).was_called!

buffer-mode-set is fired whenever a buffer has its mode set

with_signal_handler 'buffer-mode-set', nil, (handler) ->
  b = buffer 'foo'
  b.mode = {}
  assert.spy(handler).was_called!

buffer-title-set is fired whenever a buffer has its title set

with_signal_handler 'buffer-title-set', nil, (handler) ->
  b = buffer 'foo'
  b.title = 'Sir Buffer'
  assert.spy(handler).was_called!
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/bundle_spec.html b/site/source/versions/0.3/doc/spec/bundle_spec.html deleted file mode 100644 index cf37e7153..000000000 --- a/site/source/versions/0.3/doc/spec/bundle_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.bundle - - - - - - - -
-
- -
- -

howl.bundle

after_each ->
  _G.bundles = {}
  bundle.dirs = {}

with_bundle_dir = (name, f) ->
  with_tmpdir (dir) ->
    b_dir = dir / name
    b_dir\mkdir!
    f b_dir

bundle_init = (info = {}, spec = {}) ->
  mod = author: 'bundle_spec', description: 'spec_bundle', license: 'MIT'
  mod[k] = v for k,v in pairs info
  ret = 'return { info = {'
  ret ..= table.concat [k .. '="' .. v .. '"' for k,v in pairs mod], ','
  ret ..= '}, '
  if spec.unload
    ret ..= "unload = #{spec.unload} }"
  else
    ret ..= 'unload = function() end }'
  ret

.unloaded holds the adjusted names of any unloaded bundles

with_tmpdir (dir) ->
  bundle.dirs = {dir}
  for name in *{'foo-bar', 'frob_nic'}
    b_dir = dir / name
    b_dir\mkdir!
    b_dir\join('init.lua').contents = bundle_init :name

  assert.same { 'foo_bar', 'frob_nic' }, bundle.unloaded
  bundle.load_by_name 'foo_bar'
  assert.same { 'frob_nic' }, bundle.unloaded
  bundle.unload 'foo_bar'
  assert.same { 'foo_bar', 'frob_nic' }, bundle.unloaded

load_from_dir(dir)

raises an error if dir is not a directory

assert.raises 'directory', -> bundle.load_from_dir File '/not-a-directory'

raises an error if the bundle init file is missing or incomplete

with_tmpdir (dir) ->
  assert.raises 'find file', -> bundle.load_from_dir dir
  init = dir / 'init.lua'
  init\touch!
  assert.raises 'Incorrect bundle', -> bundle.load_from_dir dir
  init.contents = 'return {}'
  assert.raises 'info missing', -> bundle.load_from_dir dir
  init.contents = 'return { info = {} }'
  assert.raises 'missing info field', -> bundle.load_from_dir dir

assigns the returned bundle table to bundles using the dir basename

mod = author: 'bundle_spec', description: 'spec_bundle', license: 'MIT'
with_bundle_dir 'foo', (dir) ->
  dir\join('init.lua').contents = bundle_init mod
  bundle.load_from_dir dir
  assert.same bundles.foo.info, mod
  assert.is_equal 'function', type bundles.foo.unload

massages the assigned module name to fit with naming standards if necessary

with_bundle_dir 'Test-hello 2', (dir) ->
  dir\join('init.lua').contents = bundle_init!
  bundle.load_from_dir dir
  assert.not_nil bundles.test_hello_2

raises an error if the bundle is already loaded

with_bundle_dir 'two_times', (dir) ->
  dir\join('init.lua').contents = bundle_init!
  bundle.load_from_dir dir
  assert.raises 'loaded', -> bundle.load_from_dir dir

raises an error upon implicit global writes

with_tmpdir (dir) ->
  dir\join('init.lua').contents = [[
    file = bundle_file('bundle_aux.lua')
    return {
      info = {
        author = 'spec',
        description = 'desc',
        license = 'MIT',
      },
      file = file
    }
  ]]
  assert.raises 'implicit global', -> bundle.load_from_dir dir

(exposed bundle helpers)

bundle_file provides access to bundle files

with_bundle_dir 'test', (dir) ->
  dir\join('init.lua').contents = [[
    local file = bundle_file('bundle_aux.lua')
    return {
      info = {
        author = 'spec',
        description = 'desc',
        license = 'MIT',
      },
      unload = function() end,
      file = file
    }
  ]]
  bundle.load_from_dir dir
  assert.equal bundles.test.file, dir / 'bundle_aux.lua'

load_all()

loads all found bundles in all directories in bundle.dirs

with_tmpdir (dir) ->
  bundle.dirs = {dir}
  for name in *{'foo', 'bar'}
    b_dir = dir / name
    b_dir\mkdir!
    b_dir\join('init.lua').contents = bundle_init :name

  bundle.load_all!
  assert.not_nil bundles.foo
  assert.not_nil bundles.bar

skips any hidden entries

with_tmpdir (dir) ->
  bundle.dirs = {dir}
  b_dir = dir / '.hidden'
  b_dir\mkdir!
  b_dir\join('init.lua').contents = bundle_init name: 'hidden'

  bundle.load_all!
  assert.same [name for name, _ in pairs _G.bundles], {}

load_by_name(name)

loads the bundle with the specified name, if not already loaded

with_tmpdir (dir) ->
  bundle.dirs = {dir}
  b_dir = dir / 'named'
  b_dir\mkdir!
  b_dir\join('init.lua').contents = bundle_init name: 'named'

  bundle.load_by_name 'named'
  assert.not_nil _G.bundles.named

  assert.raises 'loaded', -> bundle.load_by_name 'named'

raises an error if the bundle could not be found

assert.raises 'not found', -> bundle.load_by_name 'oh_bundle_where_art_thouh'

unload(name)

raises an error if no bundle with the given name exists

assert.raises 'not found', -> bundle.unload 'serenity'

(for an existing bundle)

mod = name: 'bunny'

calls the bundle unload function and removes the bundle from _G.bundles

with_bundle_dir 'bunny', (dir) ->
  dir\join('init.lua').contents = bundle_init mod, unload: 'function() _G.bunny_bundle_unload = true end'
  bundle.load_from_dir dir
  bundle.unload 'bunny'
  assert.is_true _G.bunny_bundle_unload
  assert.is_nil bundles.bunny

returns early with an error if the unload function raises an error

with_bundle_dir 'bad_seed', (dir) ->
  dir\join('init.lua').contents = bundle_init mod, unload: 'function() error("barf!") end'
  bundle.load_from_dir dir
  assert.raises 'barf!', -> bundle.unload 'bad_seed'
  assert.is_not_nil bundles.bad_seed

transforms the given name to match the module name

with_bundle_dir 'dash-love', (dir) ->
  dir\join('init.lua').contents = bundle_init name: 'dash-love'
  bundle.load_from_dir dir
  assert.no_error -> bundle.unload 'dash-love'

from_file(file)

returns the adjusted name of the containing bundle if any

with_tmpdir (dir) ->
  bundle.dirs = {dir}
  b_dir = dir / 'my-bundle'
  init = b_dir\join('init.lua')
  assert.equal 'my_bundle', bundle.from_file(init)
  assert.is_nil bundle.from_file(File('/bin/ls'))
  assert.is_nil bundle.from_file(dir\join('directly_under_root'))
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/chunk_spec.html b/site/source/versions/0.3/doc/spec/chunk_spec.html deleted file mode 100644 index 745119dd7..000000000 --- a/site/source/versions/0.3/doc/spec/chunk_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Chunk - - - - - - - -
-
- -
- -

howl.Chunk

buffer = Buffer {}

before_each ->
  buffer.text = 'Liñe 1 öf text'

.start_pos returns the start_pos passed in constructor

assert.equal 3, Chunk(buffer, 3, 7).start_pos

.end_pos returns the end_pos passed in constructor

assert.equal 7, Chunk(buffer, 3, 7).end_pos

.empty is true if the chunk is empty (i.e. end_pos is lesser than start_pos)

assert.is_true Chunk(buffer, 3, 2).empty
assert.is_true Chunk(buffer, 1, 0).empty
assert.is_false Chunk(buffer, 1, 1).empty

tostring(chunk) returns .text

chunk = Chunk(buffer, 3, 6)
assert.equal chunk.text, tostring(chunk)

#chunk returns the length of the chunk

chunk = Chunk(buffer, 3, 6)
assert.equal 4, #chunk

.text

is the text in the range [start_pos..end_pos]

assert.equal 'ñe 1', Chunk(buffer, 3, 6).text

is an empty string if the chunk is empty

assert.equal '', Chunk(buffer, 3, 2).text
assert.equal '', Chunk(buffer, 1, 0).text

.text = <string>

.text = <string> replaces the chunk with <string>

chunk = Chunk(buffer, 3, 6)
chunk.text = 'feguard'
assert.equal 'Lifeguard öf text', buffer.text

updates .start_pos and .end_pos to reflect the new chunk

chunk = Chunk(buffer, 1, 6)
chunk.text = 'Zen'
assert.equal 3, chunk.end_pos
assert.equal 'Zen', chunk.text

.styling

is a table of offsets and styles, { start, "style", end [,..]}

styles = { 1, 'keyword', 3 }
styler.apply buffer, 1, buffer.size, styles
assert.same { 1, 'keyword', 2 }, Chunk(buffer, 2, 2).styles

is an empty table for an empty chunk

assert.same {}, Chunk(buffer, 2, 1).styles
assert.same {}, Chunk(buffer, 1, 0).styles

delete()

deletes the chunk

Chunk(buffer, 1, 5)\delete!
assert.equal '1 öf text', buffer.text

does nothing for an empty chunk

buffer.text = 'hello'
Chunk(buffer, 1, 0)\delete!
Chunk(buffer, 2, 1)\delete!
assert.equal 'hello', buffer.text
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/clipboard_spec.html b/site/source/versions/0.3/doc/spec/clipboard_spec.html deleted file mode 100644 index 2148b965b..000000000 --- a/site/source/versions/0.3/doc/spec/clipboard_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Clipboard - - - - - - - -
-
- -
- -

howl.Clipboard

system_cb = GtkClipboard.get(Atom.SELECTION_CLIPBOARD)

before_each ->  clipboard.clear!

push(item, opts = {})

(when <item> is a string)

adds a clip item containing text to the clipboard

clipboard.push 'hello!'
assert.equals 'hello!', clipboard.current.text

(when <item> is a table)

adds the clip item as is to the clipboard

clipboard.push text: 'hello!'
assert.equals 'hello!', clipboard.current.text

raises an error if <item> has no .text field

assert.raises 'text', -> clipboard.push {}

(when opts contains a ".to" field)

pushes the item to the specified target

clipboard.push 'hello!', to: 'a'
assert.is_nil clipboard.current
assert.equals 'hello!', clipboard.registers.a.text

(when no ".to" field is specified in opts)

ensures the number of clips does not exceed config.clipboard_max_items

config.clipboard_max_items = 5
for i = 1,6
  clipboard.push "clip #{i}"

assert.equals 5, #clipboard.clips
assert.equals 'clip 6', clipboard.clips[1].text
assert.equals 'clip 2', clipboard.clips[5].text

copies the clip to the system clipboard as well

clipboard.push 'global!'
assert.equals 'global!', system_cb\wait_for_text!

clear()

clears all clips

clipboard.push 'hello!'
clipboard.push 'to register!', to: 'a'
clipboard.clear!
assert.is_nil clipboard.current
assert.is_nil clipboard.registers.a

synchronize()

adds the clip from the system clipboard if it differs from .current

system_cb\set_text 'pushed'
clipboard.synchronize!
assert.equals 'pushed', clipboard.current.text

does nothing if the texts are the same

clipboard.push 'pushed'
system_cb\set_text 'pushed'
clipboard.synchronize!
assert.equals 'pushed', clipboard.current.text
assert.equals 1, #clipboard.clips
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/command_spec.html b/site/source/versions/0.3/doc/spec/command_spec.html deleted file mode 100644 index 4b0786143..000000000 --- a/site/source/versions/0.3/doc/spec/command_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.command - - - - - - - -
-
- -
- -

howl.command

local cmd
run = (...) ->
  f = coroutine.wrap (...) -> command.run ...
  f ...

before_each ->
  app.window = Window!
  cmd = name: 'foo', description: 'desc', handler: spy.new -> 'foo-result'

after_each ->
  app.window = nil
  command.unregister name for name in *command.names!

.names() returns a list of all command names

command.register cmd
assert.includes command.names!, 'foo'

.get(name) returns the command with the specified name

command.register cmd
assert.equal command.get('foo').handler, cmd.handler

calling .<name>(args) invokes command, passing arguments

command.register cmd
command.foo('arg1', 'arg2')
assert.spy(cmd.handler).was_called 1
assert.spy(cmd.handler).was_called_with 'arg1', 'arg2'

.unregister(command) removes the command and any aliases

command.register cmd
command.alias 'foo', 'bar'
command.unregister 'foo'

assert.is_nil command.foo
assert.is_nil command.bar
assert.same command.names!, {}

.register(command)

raises an error if any of the mandatory fields are missing

assert.raises 'name', -> command.register {}
assert.raises 'description', -> command.register name: 'foo'
assert.raises 'handler', -> command.register name: 'foo', description: 'do'
assert.raises 'factory', -> command.register name: 'foo', description: 'do'

.alias(target, name)

raises an error if target does not exist

assert.raises 'exist', -> command.alias 'nothing', 'something'

allows for multiple names for the same command

command.register cmd
command.alias 'foo', 'bar'
assert.equal 'foo', command.get('bar').alias_for
assert.includes command.names!, 'bar'

(when command name is a non-lua identifier)

before_each -> cmd.name = 'foo-cmd:bar'

register() adds accessible aliases

command.register cmd
assert.not_nil command.foo_cmd_bar

the accessible alias is not part of names()

command.register cmd
assert.same command.names!, { 'foo-cmd:bar' }

calling .<accessible_name>(args) invokes command, passing arguments

command.register cmd
dispatch.launch -> command.foo_cmd_bar('arg1', 'arg2')
assert.spy(cmd.handler).was_called 1
assert.spy(cmd.handler).was_called_with 'arg1', 'arg2'

unregister() removes the accessible name as well

command.register cmd
command.unregister 'foo-cmd:bar'
assert.is_nil command.foo_cmd_bar

.run(cmd_string)

(when <cmd_string> is empty or missing)

displays the commandline with a ":" prompt

run!
assert.equals ':', app.window.command_line.prompt

(when <cmd_string> is given)

(.. and it matches a command)

that command is invoked

command.register cmd
run cmd.name
assert.spy(cmd.handler).was_called 1

(.. and the command specifies an input function)

local cmd

before_each ->
  cmd =
    name: 'with-input'
    description: 'test'
    input: spy.new -> 'input-result1', 'input-result2'
    handler: spy.new ->
  command.register cmd

calls the command input function, passing through extra args

run cmd.name, 'arg1', 'arg2'
assert.spy(cmd.input).was_called 1
assert.spy(cmd.input).was_called_with 'arg1', 'arg2'

passes the result of the input function into the handler

run cmd.name
assert.spy(cmd.handler).was_called 1
assert.spy(cmd.handler).was_called_with 'input-result1', 'input-result2'

does not call handler if input function returns nil

cmd = {
  name: 'cancelled-input'
  description: 'test'
  input: ->
  handler: spy.new ->
}
command.register cmd
run cmd.name
assert.spy(cmd.handler).was_called 0

sets spillover to any text arguments before invoking the input

local spillover
command.register
  name: 'with-input'
  description: 'test'
  input: -> spillover = app.window.command_line\pop_spillover!
  handler: ->
run 'with-input hello cmd'
assert.equal 'hello cmd', spillover

displays the ":<cmd_string> " in the command line during input

local prompt
cmd = {
  name: 'getp'
  description: 'desc'
  input: -> prompt = app.window.command_line.command_widget.text
  handler: ->
}
command.register cmd
run 'getp'
assert.equals ':'..cmd.name..' ', prompt

(.. and the command does not specify an input function)

calls the command handler, passing through extra args

cmd = {
  name: 'without-input'
  description: 'test'
  handler: spy.new ->
}
command.register cmd
run cmd.name, 'arg1', 'arg2'
assert.spy(cmd.handler).was_called 1
assert.spy(cmd.handler).was_called_with 'arg1', 'arg2'

(.. and it matches an alias)

the aliased command is invoked

command.register cmd
command.alias cmd.name, 'aliascmd'
run 'aliascmd'
assert.spy(cmd.handler).was_called 1

(.. and it contains <non-interactive-command>space<args>)

before_each ->
  log.clear!
  command.register cmd

logs an error

run cmd.name .. ' args'
assert.not_nil log.last_error

the command line contains the command name

run cmd.name .. ' args'
assert.equals cmd.name, app.window.command_line.text

(.. and it contains <invalid-command>space<args>)

logs an error

run 'no-such-command hello cmd'
assert.not_nil log.last_error

the command line contains the passed text

run 'no-such-command hello cmd'
assert.equals 'no-such-command hello cmd', app.window.command_line.text

(.. when it specifies a unknown command)

displays the <cmd_string> in the commandline text

run 'what-the-heck now'
assert.equals 'what-the-heck now', app.window.command_line.text
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/completer_spec.html b/site/source/versions/0.3/doc/spec/completer_spec.html deleted file mode 100644 index 969733a77..000000000 --- a/site/source/versions/0.3/doc/spec/completer_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Completer - - - - - - - -
-
- -
- -

howl.Completer

buffer = nil
before_each ->
  buffer = Buffer {}

.start_pos holds the start position for completing

buffer.text = 'oh cruel word'
assert.equal 4, Completer(buffer, 9).start_pos

.complete(pos [, limit])

instantiates completers once with (buffer, context)

buffer.text = 'mr.cat'
factory = spy.new -> nil
append buffer.completers, factory
completer = Completer(buffer, 6)
completer\complete 6
assert.spy(factory).was.called_with buffer, buffer\context_at 6
completer\complete 6
assert.spy(factory).was.called(1)

lookups completers in completion when they are specified as strings

buffer.text = 'yowser'
factory = spy.new -> nil
completion.register name: 'comp-name', :factory
append buffer.completers, 'comp-name'
completer = Completer(buffer, 3)
assert.spy(factory).was.called

returns completions for completers in buffer and mode

mode = completers: { -> complete: -> { 'mode' } }
buffer.mode = mode
append buffer.completers,  -> complete: -> { 'buffer' }
completions = Completer(buffer, 1)\complete 1
assert.same completions, { 'buffer', 'mode' }

returns completions for mode even if buffer has no completers

mode = completers: { -> complete: -> { 'mode' } }
buffer.mode = mode
assert.same Completer(buffer, 1)\complete(1), { 'mode' }

returns the search string after the completions

mode = completers: { -> complete: -> { 'prefix' } }
buffer.text = 'pre'
buffer.mode = mode
append buffer.completers,  -> complete: -> { 'buffer' }
_, search = Completer(buffer, 4)\complete 4
assert.same search, 'pre'

calls <completer.complete()> with (completer, context)

buffer.text = 'mr.cat'
comp = complete: spy.new -> {}
append buffer.completers, -> comp
completer = Completer(buffer, 6)

completer\complete 6
assert.spy(comp.complete).was.called_with comp, buffer\context_at 6

completer\complete 7
assert.spy(comp.complete).was.called_with comp, buffer\context_at 7

returns completions from just one completer if completions.authoritive is set

append buffer.completers, -> complete: -> { 'one', authoritive: true }
append buffer.completers, -> complete: -> { 'two' }
completions = Completer(buffer, 1)\complete 1
assert.same { 'one' }, completions

merges duplicate completions from different completers

append buffer.completers, -> complete: -> { 'yes'}
append buffer.completers, -> complete: -> { 'yes' }
completions = Completer(buffer, 1)\complete 1
assert.same { 'yes' }, completions

gives a final boost to case-matching completions, all else equal

buffer.text = 'he'
append buffer.completers, -> complete: -> { 'Hello', 'hello' }
completions = Completer(buffer, 3)\complete 3
assert.same { 'hello', 'Hello' }, completions

buffer.text = 'He'
append buffer.completers, -> complete: -> { 'hello', 'Hello' }
completions = Completer(buffer, 3)\complete 3
assert.same { 'Hello', 'hello' }, completions

(limiting completions)

returns at most `completion_max_shown` completions

completions = ["cand-#{i}" for i = 1,15]
append buffer.completers, -> complete: -> completions
buffer.config.completion_max_shown = 3
actual = Completer(buffer, 1)\complete 1
assert.equal 3, #actual

returns at most <limit> completions if specified

completions = ["cand-#{i}" for i = 1,15]
append buffer.completers, -> complete: -> completions
actual = Completer(buffer, 1)\complete 1, 4
assert.equal 4, #actual

accept(completion)

returns the position after the accepted completion

buffer.text = 'hello there'
  assert.equal 5, Completer(buffer, 4)\accept 'hƏlp', 4

(when hungry_completion is true)

replaces the current word with <completion>

buffer.text = 'hello there'
buffer.config.hungry_completion = true
completer = Completer(buffer, 3)
completer\accept 'hey', 3
assert.equal 'hey there', buffer.text

(when hungry_completion is false)

inserts <completion> at the start position

buffer.text = 'hello there'
buffer.config.hungry_completion = false
completer = Completer(buffer, 7)
completer\accept 'over', 7
assert.equal 'hello overthere', buffer.text

(interacting with mode's .on_completion_accepted)

invokes it with (mode, completion, context) if present

mode = on_completion_accepted: spy.new -> nil
buffer.mode = mode
buffer.text = 'hello there'
Completer(buffer, 4)\accept 'help', 4
assert.spy(mode.on_completion_accepted).was_called_with mode, 'help', buffer\context_at(5)

uses it's return value as the position returned if it's a number

mode = on_completion_accepted: -> 6
buffer.mode = mode
buffer.text = 'hello there'
assert.equal 6, Completer(buffer, 4)\accept 'help', 4
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/completion/api_completer_spec.html b/site/source/versions/0.3/doc/spec/completion/api_completer_spec.html deleted file mode 100644 index 02b8016ec..000000000 --- a/site/source/versions/0.3/doc/spec/completion/api_completer_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.completion.api_completer - - - - - - - -
-
- -
- -

howl.completion.api_completer

factory = completion.api.factory

complete()

api = {
  keyword: {},
  function: {},
  sub: {
    foo: {}
    bar: {}
    zed: {
      frob: {}
      other: {}
    }
  }
}

local buffer, lines, mode

complete_at = (pos) ->
  context = buffer\context_at pos
  completer = factory buffer, context
  comps = completer\complete context
  table.sort comps
  comps

before_each ->
  mode = DefaultMode!
  mode.api = api
  mode.completers = { 'api' }
  buffer = Buffer mode
  lines = buffer.lines

returns global completions when no prefix is found

buffer.text = ' k\nfun'
comps = complete_at buffer.lines[1].start_pos
assert.same { 'function', 'keyword', 'sub' }, comps
assert.same { 'function' }, complete_at buffer.lines[2].end_pos

returns authoritive scoped completions when appropriate

buffer.text = 'sub.zed:f'
assert.same { 'bar', 'foo', 'zed', authoritive: true }, complete_at 5
assert.same { 'zed', authoritive: true }, complete_at 6
assert.same { 'frob', 'other', authoritive: true }, complete_at 9

returns an empty set for non-matched prefixes

buffer.text = 'well so.sub'
assert.same { }, complete_at 5
assert.same { }, complete_at 8
assert.same { }, complete_at buffer.length + 1

(when mode provides a .resolve_type() method)

is invoked with (mode, context)

mode.resolve_type = spy.new -> nil
buffer.text = 'lookie'
complete_at 5
assert.spy(mode.resolve_type).was_called_with mode, buffer\context_at 5

the returned (path, part) is used for looking up completions

mode.resolve_type = -> 'sub', {'sub'}
buffer.text = 'look.'
assert.same { 'bar', 'foo', 'zed', authoritive: true }, complete_at 6
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/completion/in_buffer_completer_spec.html b/site/source/versions/0.3/doc/spec/completion/in_buffer_completer_spec.html deleted file mode 100644 index 408502ad7..000000000 --- a/site/source/versions/0.3/doc/spec/completion/in_buffer_completer_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.completion.InBufferCompleter - - - - - - - -
-
- -
- -

howl.completion.InBufferCompleter

complete()

local buffer, lines
factory = completion.in_buffer.factory

complete_at = (pos) ->
  context = buffer\context_at pos
  completer = factory buffer, context
  completer\complete context

before_each ->
  buffer = Buffer {}
  lines = buffer.lines

returns completions for local matches in the buffer

buffer.text = [[
Hello there
  some symbol (foo) {
    if yike {
say_it = 'blarg'
s
    }
  }

  other sion (arg) {
    saphire = 'that too'
  }
]]
comps = complete_at buffer.lines[5].end_pos
table.sort comps
assert.same { 'saphire', 'say_it', 'sion', 'some', 'symbol' }, comps

does not include the token being completed itself

buffer.text = [[
text
te
noice
test
]]
assert.same { 'text', 'test' }, complete_at lines[2].end_pos - 1
assert.same { 'test' }, complete_at 3

favours matches close to the current position

buffer.text = [[
two
twitter
tw
other
and other
twice
twitter
]]
assert.same { 'twitter', 'two', 'twice' }, complete_at lines[3].end_pos

offers "smart" completions after the local ones

buffer.text = [[
two
twitter
_fatwa
tw
the_water
]]
assert.same { 'twitter', 'two', 'the_water', '_fatwa' }, complete_at lines[4].end_pos

works with unicode

buffer.text = [[
hellö
häst
h
]]
assert.same { 'häst', 'hellö' }, complete_at lines[3].end_pos

detects existing words using the word_pattern variable

buffer.text = [[
*foo*/-bar
eat.food.
*
oo
]]
buffer.config.word_pattern = '[^/%s.]+'
assert.same { '*foo*' }, complete_at lines[3].end_pos

(multiple buffers)

local buffer2, buffer3
before_each ->
  close_all_buffers!

  buffer2 = Buffer buffer.mode
  buffer2.text = 'foo\n'
  app\add_buffer buffer2, false
  buffer2.last_shown = 123

  buffer3 = Buffer buffer.mode
  buffer3.text = 'fabulous\n'
  buffer3.last_shown = 12
  app\add_buffer buffer3, false

after_each ->
  app\close_buffer buffer2, true
  app\close_buffer buffer3, true

searches up to <config.inbuffer_completion_max_buffers> other buffers

buffer.text = 'fry\nf'
comps = complete_at buffer.lines[2].end_pos
table.sort comps
assert.same { 'fabulous', 'foo', 'fry' }, comps

buffer.config.inbuffer_completion_max_buffers = 2
comps = complete_at buffer.lines[2].end_pos
table.sort comps
assert.same { 'foo', 'fry' }, comps

prefers closer matches

buffer.text = 'fry\nf'
comps = complete_at buffer.lines[2].end_pos
assert.same { 'fry', 'foo', 'fabulous' }, comps

skips buffers with a different mode if <config.inbuffer_completion_same_mode_only> is true

buffer.config.inbuffer_completion_same_mode_only = true
buffer2.mode = {}
buffer.text = 'fry\nf'
comps = complete_at buffer.lines[2].end_pos
assert.same { 'fry', 'fabulous' }, comps

buffer.config.inbuffer_completion_same_mode_only = false
comps = complete_at buffer.lines[2].end_pos
assert.same { 'fry', 'foo', 'fabulous' }, comps
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/completion_spec.html b/site/source/versions/0.3/doc/spec/completion_spec.html deleted file mode 100644 index 56a9b1f76..000000000 --- a/site/source/versions/0.3/doc/spec/completion_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.completion - - - - - - - -
-
- -
- -

howl.completion

after_each -> completion.unregister 'foo'

.<name> allows direct indexing of completions

c = name: 'foo', factory: -> {}
completion.register c
assert.same completion.foo, c

.unregister(name) unregisters the specified completion

completion.register name: 'foo', factory: -> {}
completion.unregister 'foo'

assert.is_nil completion.foo

.list contains all registered completions

c = name: 'foo', factory: -> {}
completion.register c
assert.includes completion.list, c

.register(options)

raises an error if any of the mandatory fields are missing

assert.raises 'name', -> completion.register nil, -> true
assert.raises 'factory', -> completion.register name: 'foo'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/config_spec.html b/site/source/versions/0.3/doc/spec/config_spec.html deleted file mode 100644 index ec01e9736..000000000 --- a/site/source/versions/0.3/doc/spec/config_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.config - - - - - - - -
-
- -
- -

howl.config

before_each ->
  config.reset!
  app.editor = nil

reset clears all set values, but keeps the definitions

config.define name: 'var', description: 'test'
config.set 'var', 'set'
config.reset!
assert.is_not_nil config.definitions['var']
assert.is_nil config.get 'var'

global variables can be set and get directly on config

config.define name: 'direct', description: 'test', default: 123
assert.equal config.direct, 123
config.direct = 'bar'
assert.equal config.direct, 'bar'
assert.equal config.get('direct'), 'bar'

define(options)

raises an error if name is missing

assert.raises 'name', -> config.define {}

raises an error if the description option is missing

assert.raises 'description', -> config.define name: 'foo'

.definitions

is a table of the current definitions, keyed by name

var = name: 'foo', description: 'foo variable'
config.define var
assert.equal type(config.definitions), 'table'
assert.same var, config.definitions.foo

writing directly to it raises an error

assert.has_error -> config.definitions.frob = 'crazy'

set(name, value)

sets <name> globally to <value>

var = name: 'foo', description: 'foo variable'
config.define var
config.set 'foo', 2
assert.equal config.get('foo'), 2

an error is raised if <name> is not defined

assert.raises 'Undefined', -> config.set 'que', 'si'

setting a value of nil clears the value

var = name: 'foo', description: 'foo variable'
config.define var
config.set 'foo', 'bar'
config.set 'foo', nil
assert.is_nil config.foo

get(name)

before_each -> config.define name: 'var', description: 'test variable'

returns the global value of <name>

config.set 'var', 'hello'
assert.equal config.get('var'), 'hello'

(when a default is provided)

before_each -> config.define name: 'with_default', description: 'test', default: 123

the default value is returned if no value has been set

assert.equal config.get('with_default'), 123

if a value has been set it takes precedence

config.set 'with_default', 'foo'
assert.equal config.get('with_default'), 'foo'

(when a validate function is provided)

is called with the value to be set whenever the variable is set

validate = spy.new -> true
config.define name: 'validated', description: 'test', :validate
config.set 'validated', 'my_value'
assert.spy(validate).was_called_with 'my_value'

an error is raised if the function returns false for to-be set value

config.define name: 'validated', description: 'test', validate: -> false
assert.error -> config.set 'validated', 'foo'
config.define name: 'validated', description: 'test', validate: -> nil
assert.no_error -> config.set 'validated', 'foo'
config.define name: 'validated', description: 'test', validate: -> true
assert.no_error -> config.set 'validated', 'foo'

an error is not raised if the function returns truish for to-be set value

config.define name: 'validated', description: 'test', validate: -> true
config.set 'validated', 'foo'
assert.equal config.get('validated'), 'foo'
config.define name: 'validated', description: 'test', validate: -> 2
config.set 'validated', 'foo2'
assert.equal config.get('validated'), 'foo2'

the function is not called when clearing a value by setting it to nil

validate = Spy!
config.define name: 'validated', description: 'test', :validate
config.set 'validated', nil
assert.is_false validate.called

(when a convert function is provided)

is called with the value to be set and the return value is used instead

config.define name: 'converted', description: 'test', convert: -> 'wanted'
config.set 'converted', 'requested'
assert.equal config.converted, 'wanted'

(when options is provided)

before_each ->
  config.define
    name: 'with_options'
    description: 'test'
    options: { 'one', 'two' }

an error is raised if the to-be set value is not a valid option

assert.raises 'option', -> config.set 'with_options', 'three'

an error is not raised if the to-be set value is a valid option

config.set 'with_options', 'one'

options can be a function returning a table

config.define name: 'with_options_func', description: 'test', options: ->
  { 'one', 'two' }

assert.raises 'option', -> config.set 'with_options_func', 'three'
config.set 'with_options_func', 'one'

options can be a table of tables containg values and descriptions

options = {
  { 'one', 'description for one' }
  { 'two', 'description for two' }
}

config.define name: 'with_options_desc', description: 'test', :options

assert.raises 'option', -> config.set 'with_options_desc', 'three'
config.set 'with_options_desc', 'one'

(when scope is provided)

raises an error if it is not "local" or "global"

assert.raises 'scope', -> config.define name: 'bla', description: 'foo', scope: 'blarg'

(.. with local scope for a variable)

an error is raised when trying to set the global value of the variable

config.define name: 'local', description: 'test', scope: 'local'
assert.error -> config.set 'local', 'foo'

(when type_of is provided)

raises an error if the type is not recognized

assert.raises 'type', -> config.define name: 'bla', description: 'foo', type_of: 'blarg'

(.. and is "boolean")

def = nil
before_each ->
  config.define name: 'bool', description: 'foo', type_of: 'boolean'
  def = config.definitions.bool

options are {true, false}

assert.same def.options, { true, false }

convert handles boolean types and "true" and "false"

assert.equal def.convert(true), true
assert.equal def.convert(false), false
assert.equal def.convert('true'), true
assert.equal def.convert('false'), false
assert.equal def.convert('blargh'), 'blargh'

converts to boolean upon assignment

config.bool = 'false'
assert.equal config.bool, false

(.. and is "number")

def = nil
before_each ->
  config.define name: 'number', description: 'foo', type_of: 'number'
  def = config.definitions.number

convert handles numbers and string numbers

assert.equal def.convert(1), 1
assert.equal def.convert('1'), 1
assert.equal def.convert(0.5), 0.5
assert.equal def.convert('0.5'), 0.5
assert.equal def.convert('blargh'), 'blargh'

validate returns true for numbers only

assert.is_true def.validate 1
assert.is_true def.validate 1.2
assert.is_false def.validate '1'
assert.is_false def.validate 'blargh'

converts to number upon assignment

config.number = '1'
assert.equal config.number, 1

(.. and is "string_list")

def = nil
before_each ->
  config.define name: 'string_list', description: 'foo', type_of: 'string_list'
  def = config.definitions.string_list

validate returns true for table values

assert.is_true def.validate {}
assert.is_false def.validate '1'
assert.is_false def.validate 23

convert

leaves string tables alone

orig = { 'hi', 'there' }
assert.same orig, def.convert orig

converts values in other tables as necessary

orig = { 1, 2 }
assert.same { '1', '2', }, def.convert orig

converts simple values into a table

assert.same { '1' }, def.convert '1'
assert.same { '1' }, def.convert 1

converts a blank string into an empty table

assert.same {}, def.convert ''
assert.same {}, def.convert '  '

converts a comma separated string into a list of values

assert.same { '1', '2' }, def.convert '1,2'
assert.same { '1', '2' }, def.convert ' 1 ,   2 '

(watching)

before_each -> config.define name: 'trigger', description: 'watchable'

watch(name, function) register a watcher for <name>

assert.not_error -> config.watch 'foo', -> true

set invokes watchers with <name>, <value> and false

callback = Spy!
config.watch 'trigger', callback
config.set 'trigger', 'value'
assert.same callback.called_with, { 'trigger', 'value', false }

define(..) invokes watchers with <name>, <default-value> and false

callback = spy.new ->
config.watch 'undefined', callback
config.define name: 'undefined', description: 'springs into life', default: 123
assert.spy(callback).was_called_with 'undefined', 123, false

(.. when a callback raises an error)

before_each -> config.watch 'trigger', -> error 'oh noes'

other callbacks are still invoked

callback = Spy!
config.watch 'trigger', callback
config.set 'trigger', 'value'
assert.is_true callback.called

an error is logged

config.set 'trigger', 'value'
assert.match log.last_error.message, 'watcher'

proxy

config.define name: 'my_var', description: 'base', type_of: 'number'
local proxy

before_each ->
  config.my_var = 123
  proxy = config.local_proxy!

returns a table with access to all previously defined variables

assert.equal 123, proxy.my_var

changing a variable changes it locally only

proxy.my_var = 321
assert.equal 321, proxy.my_var
assert.equal 123, config.my_var

assignments are still validated and converted as usual

assert.has_error -> proxy.my_var = 'not a number'
proxy.my_var = '111'
assert.equal 111, proxy.my_var

an error is raised if trying to set a variable with global scope

config.define name: 'global', description: 'global', scope: 'global'
assert.has_error -> proxy.global = 'illegal'

an error is raised if the variable is not defined

assert.raises 'Undefined', -> proxy.que = 'si'

setting a value to nil clears the value

proxy.my_var = 666
proxy.my_var = nil
assert.equal 123, proxy.my_var

setting a variable via a proxy invokes watchers with <name>, <value> and true

callback = spy.new ->
config.watch 'my_var', callback
proxy.my_var = 333
assert.spy(callback).was.called_with, { 'my_var', 333, true }

can be chained to another proxy to create a lookup chain

config.my_var = 222
base_proxy = config.local_proxy!
proxy.chain_to base_proxy
assert.equal 222, proxy.my_var
base_proxy.my_var = 333
assert.equal 333, proxy.my_var
assert.equal 222, config.my_var
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/dispatch_spec.html b/site/source/versions/0.3/doc/spec/dispatch_spec.html deleted file mode 100644 index da0084eed..000000000 --- a/site/source/versions/0.3/doc/spec/dispatch_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.dispatch - - - - - - - -
-
- -
- -

howl.dispatch

launch(f, ...)

invokes <f> in a coroutine with the specified arguments

f = spy.new ->
  _, is_main = coroutine.running!
  assert.is_false is_main

dispatch.launch f, 1, nil, 'three'
assert.spy(f).was_called_with 1, nil, 'three'

(when <f> starts correctly)

returns true and the coroutine status

status, co_status = dispatch.launch -> nil
assert.is_true status
assert.equals 'dead', co_status

(when <f> errors upon start)

returns false and the error message

status, err = dispatch.launch -> error 'foo'
assert.is_false status
assert.equals 'foo', err

wait()

yields until resumed using resume() on the parked handle

handle = dispatch.park 'test'
done = false

dispatch.launch ->
  dispatch.wait handle
  done = true

assert.is_false done
dispatch.resume handle
assert.is_true done

returns any parameters passed to resume()

handle = dispatch.park 'test'
local res

dispatch.launch ->
  res = { dispatch.wait handle }

dispatch.resume handle, 1, nil, 'three', nil
assert.same { 1, nil, 'three', nil }, res

raises an error when resumed with resume_with_error()

handle = dispatch.park 'test'
local err

dispatch.launch ->
  status, err = pcall dispatch.wait, handle
  assert.is_false status

dispatch.resume_with_error handle, 'blargh!'
assert.includes err, 'blargh!'

resume()

propagates any error occurring during resuming

handle = dispatch.park 'test'

dispatch.launch ->
  dispatch.wait handle
  error 'boom'

assert.raises 'boom', -> dispatch.resume handle

(when nothing is yet waiting on the parking)

it 'blocks until released by a wait', (done) ->

howl_async ->
  handle = dispatch.park 'out-of-order'
  launched, status = dispatch.launch -> dispatch.resume handle, 'resume-now!'
  assert.is_true launched
  assert.equals "suspended", status

  launched, status = dispatch.launch ->
    assert.equals 'resume-now!', dispatch.wait handle
    done!

  assert.is_true launched
  assert.equals "dead", status
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/editing/auto_pair_spec.html b/site/source/versions/0.3/doc/spec/editing/auto_pair_spec.html deleted file mode 100644 index 7924fb206..000000000 --- a/site/source/versions/0.3/doc/spec/editing/auto_pair_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.editing.auto_pair - - - - - - - -
-
- -
- -

howl.editing.auto_pair

handle(event, editor)

local buffer, editor, cursor

event = (character, key_name = character) -> :character, :key_name, key_code: 65

before_each ->
  buffer = Buffer!
  editor = Editor buffer
  cursor = editor.cursor
  buffer.text = ''

returns non-true when the character does not match a known pair

assert.is_not_true auto_pair.handle event('x'), editor

always returns non-true if the auto_pair config variable is false

buffer.mode.auto_pairs = { ['(']: ')' }
buffer.config.auto_pair = false
assert.is_not_true auto_pair.handle event('('), editor

(when the character matches a known pair from buffer.mode.auto_pairs)

before_each ->
  buffer.mode.auto_pairs = {
    '(': ')'
    '[': ']'
    '"': '"'
  }

(.. when there is an active selection)

before_each ->
  buffer.text = ' foo '
  editor.selection\set 2, 5

surrounds the selection with the pair as one undo operation

auto_pair.handle event('('), editor
assert.equal ' (foo) ', buffer.text
buffer\undo!
assert.equal ' foo ', buffer.text

returns true

assert.is_true auto_pair.handle event('('), editor

(.. with no selection active)

returns true

assert.is_true auto_pair.handle event('('), editor

inserts the pair in the buffer, as one undo operation

for start_c, end_c in pairs buffer.mode.auto_pairs
  auto_pair.handle event(start_c), editor
  assert.equal "#{start_c}#{end_c}", buffer.text
  buffer\undo!
  assert.equal '', buffer.text

positions the cursor within the pair

auto_pair.handle event('['), editor
assert.equal 2, cursor.pos

does not trigger for a same character pair if the current balance is uneven

buffer.text = '"foo'
cursor.pos = 5
assert.is_not_true auto_pair.handle event('"'), editor

does not trigger when the next character is a word character

buffer.text = 'foo'
cursor.pos = 1
assert.is_not_true auto_pair.handle event('('), editor

(overtyping companion characters)

before_each ->
  buffer.mode.auto_pairs = {
    '(': ')'
    '"': '"'
  }

overtypes any companion characters if the current pair-balance is even

buffer.text = '()'
cursor.pos = 2
assert.is_true auto_pair.handle event(')'), editor
assert.equal '()', buffer.text
assert.equal 3, cursor.pos

overtypes any companion characters for even pair-balance when the start characters and end character is the same

buffer.text = '""'
cursor.pos = 2
assert.is_true auto_pair.handle event('"'), editor
assert.equal '""', buffer.text
assert.equal 3, cursor.pos

does not overtype if the current pair-balance is non-even

buffer.text = '(foo'
cursor.pos = 5
assert.is_not_true auto_pair.handle event(')'), editor

does not overtype if the current character is different

buffer.text = '(foo)'
cursor.pos = 6
assert.is_not_true auto_pair.handle event(')'), editor

(deleting back inside a pair)

before_each -> buffer.mode.auto_pairs = ['(']: ')'

returns true

buffer.text = '()'
cursor.pos = 2
assert.is_true auto_pair.handle event('\8', 'backspace'), editor

deletes both characters as one undo

buffer.text = '()'
cursor.pos = 2
auto_pair.handle event('\8', 'backspace'), editor
assert.equal '', buffer.text
buffer\undo!
assert.equal '()', buffer.text
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/editing/formatting_spec.html b/site/source/versions/0.3/doc/spec/editing/formatting_spec.html deleted file mode 100644 index 08cc93aed..000000000 --- a/site/source/versions/0.3/doc/spec/editing/formatting_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.editing.formatting - - - - - - - -
-
- -
- -

howl.editing.formatting

local buffer, editor, cursor, lines
before_each ->
  buffer = Buffer!
  buffer.config.indent = 2
  editor = Editor buffer
  cursor = editor.cursor

ensure_block(editor, block_start_p, block_end_p, end_s)

(when block_start_p does not match)

does nothing and returns false

buffer.text = '{\n}'
cursor.line = 2
assert.is_false formatting.ensure_block editor, 'foo', 'bar', 'bar'
assert.equals '{\n}', buffer.text

(when block_start_p matches)

formats an existing block as necessary

buffer.text = '{\n}'
cursor.line = 2
assert.is_true formatting.ensure_block editor, '{$', '}', '}'
assert.equals '{\n  \n}', buffer.text
assert.equals 2, cursor.line

completes an existing block as necessary

buffer.text = '{\n'
cursor.line = 2
assert.is_true formatting.ensure_block editor, '{$', '}', '}'
assert.equals '{\n  \n}\n', buffer.text
assert.equals 2, cursor.line

is not fooled by subsequent blocks

buffer.text = '{\n\n{\n}'
cursor.line = 2
assert.is_true formatting.ensure_block editor, '{$', '}', '}'
assert.equals '{\n  \n}\n{\n}', buffer.text
assert.equals 2, cursor.line

leaves an already ok indented block alone

buffer.text = '{\n  \n}\n'
cursor.line = 2
assert.is_false formatting.ensure_block editor, '{%s*$', '}', '}'
assert.equals '{\n  \n}\n', buffer.text
assert.equals 2, cursor.line

leaves an already ok non-indented block alone

buffer.text = '{\n\nfoo\n}\n'
cursor.line = 2
assert.is_false formatting.ensure_block editor, '{%s*$', '}', '}'
assert.equals '{\n\nfoo\n}\n', buffer.text
assert.equals 2, cursor.line

leaves blocks with content in them alone

buffer.text = '{\n  \n  foo\n}\n'
for line in *{2, 3}
  cursor.line = line
  assert.is_false formatting.ensure_block editor, '{$', '}', '}'
  assert.equals '{\n  \n  foo\n}\n', buffer.text
  assert.equals line, cursor.line

handles nested blocks

buffer.text = '{\n  {\n\n}\n'
cursor.line = 3
assert.is_true formatting.ensure_block editor, '{$', '}', '}'
assert.equals '{\n  {\n    \n  }\n}\n', buffer.text
assert.equals 3, cursor.line

(.. when block_start_p equals block_end_p)

formats an existing block as necessary

buffer.text = '|\n|'
cursor.line = 2
assert.is_true formatting.ensure_block editor, '|$', '|', '|'
assert.equals '|\n  \n|', buffer.text
assert.equals 2, cursor.line

completes an existing block as necessary

buffer.text = '|\n'
cursor.line = 2
assert.is_true formatting.ensure_block editor, '|$', '|', '|'
assert.equals '|\n  \n|\n', buffer.text
assert.equals 2, cursor.line

leaves an already ok indented block alone

buffer.text = '|\n  \n|\n'
cursor.line = 2
assert.is_false formatting.ensure_block editor, '|$', '|', '|'
assert.equals '|\n  \n|\n', buffer.text
assert.equals 2, cursor.line

leaves an already ok non-indented block alone

buffer.text = '|\n\nfoo\n|\n'
cursor.line = 2
assert.is_false formatting.ensure_block editor, '|$', '|', '|'
assert.equals '|\n\nfoo\n|\n', buffer.text
assert.equals 2, cursor.line

leaves an already ok non-indented block alone

buffer.text = '|\n\nfoo\n|\n'
cursor.line = 2
assert.is_false formatting.ensure_block editor, '|$', '|', '|'
assert.equals '|\n\nfoo\n|\n', buffer.text
assert.equals 2, cursor.line

recognizes a previous block if it is all non-blank lines

buffer.text = '|\nfoo\n|\n'
cursor.line = 4
assert.is_false formatting.ensure_block editor, '|$', '|', '|'
assert.equals '|\nfoo\n|\n', buffer.text
assert.equals 4, cursor.line

(.. indentation & cursor)

indents the new line using the "indent" config variable by default

buffer.config.indent = 4
buffer.text = '{\n'
cursor.line = 2
formatting.ensure_block editor, '{$', '}', '}'
assert.equals '{\n    \n}\n', buffer.text

indents the new line using the editor

buffer.mode = indent: (editor) => editor.current_line.indentation = 5
buffer.text = '{\n'
cursor.line = 2
formatting.ensure_block editor, '{$', '}', '}'
assert.equals '{\n     \n}\n', buffer.text

positions the cursor after the indentation of the new line

buffer.text = '{\n'
cursor.line = 2
assert.is_true formatting.ensure_block editor, '{$', '}', '}'
assert.equals 3, cursor.column
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/editing/text_spec.html b/site/source/versions/0.3/doc/spec/editing/text_spec.html deleted file mode 100644 index 510eb7ca2..000000000 --- a/site/source/versions/0.3/doc/spec/editing/text_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.editing.text - - - - - - - -
-
- -
- -

howl.editing.text

local buffer, lines
before_each ->
  buffer = Buffer!
  lines = buffer.lines

paragraph_at(line)

at = (nr) ->
  [l.nr for l in *text.paragraph_at lines[nr]]

before_each ->
  buffer.text = 'one\n\nthree\nfour\n\n\nseven'

returns a list of lines composing the current paragraph

assert.same { 1 }, at 1
assert.same { 3, 4 }, at 3
assert.same { 3, 4 }, at 4
assert.same { 7 }, at 7

considers lines starting with blanks to be paragraph delimiters

buffer.text = 'trailing\n  indented start\ncontinued and ended here.\n  new para'
assert.same { 2, 3 }, at 2

calls and respects the mode's .is_paragraph_break() if present

buffer.mode = is_paragraph_break: (line) -> line\match '^-'
buffer.text = 'before\n- new para\n  continued\n- next'
assert.same { 2, 3 }, at 2

(when starting at an empty line)

returns the previous paragraph if present

assert.same { 1 }, at 2
assert.same { 3, 4 }, at 5

returns the following paragraph if present

assert.same { 7 }, at 6

returns an empty list if no paragraph is found

buffer.text = 'one\n\n\n\nfive'
assert.same {}, at 3

can_reflow(line, limit)

returns true if the line is longer than limit

buffer.text = 'too long'
assert.is_true text.can_reflow lines[1], 6

returns true if the line can be combined with the previous one

buffer.text = 'itty\nbitty'
assert.is_true text.can_reflow lines[2], 10

returns true if the line can be combined with the following one

buffer.text = 'itty\nbitty'
assert.is_true text.can_reflow lines[1], 10

buffer.text = 'itty bitty\nshort\nlong by itself'
assert.is_true text.can_reflow lines[2], 10

returns false if the line can not be combined with the previous one

buffer.text = 'itty\nbitty'
assert.is_false text.can_reflow lines[2], 9

returns false if the line can not be combined with the following one

buffer.text = 'itty\nbitty'
assert.is_false text.can_reflow lines[1], 9

returns false if the line is one, unbreakable, word

buffer.text = 'imjustgoingtoramble\none'
assert.is_false text.can_reflow lines[1], 10

returns true if the line is more than one word, the first being unbreakable

buffer.text = 'imjustgoingtoramble stopme\none'
assert.is_true text.can_reflow lines[1], 10

returns false if an adjacent short line is blank

buffer.text = 'itty\n'
assert.is_false text.can_reflow lines[1], 10

buffer.text = '\nitty\n'
assert.is_false text.can_reflow lines[2], 10

reflow_paragraph_at(line, limit)

splits lines to enforce at most <limit> columns

buffer.text = 'one two three four\n'
text.reflow_paragraph_at lines[1], 10
assert.equals 'one two\nthree four\n', buffer.text

splits lines as close to <limit> as possible, given non-breaking words

buffer.text = 'onetwo three four\n'
text.reflow_paragraph_at lines[1], 5
assert.equals 'onetwo\nthree\nfour\n', buffer.text

combines lines as necessary to match <limit>

buffer.text = 'one\ntwo\nthree\nfour\n'
text.reflow_paragraph_at lines[1], 10
assert.equals 'one two\nthree four\n', buffer.text

returns an unbreakable line as is if it can not reflow

buffer.text = 'onetwo\n'
text.reflow_paragraph_at lines[1], 4
assert.equals 'onetwo\n', buffer.text

does not require there to be any newline at the end of the paragraph

buffer.text = 'one two'
text.reflow_paragraph_at lines[1], 5
assert.equals 'one\ntwo', buffer.text

includes all the paragraph text in the reflowed text (boundary condition)

buffer.text = 'one t'
text.reflow_paragraph_at lines[1], 4
assert.equals 'one\nt', buffer.text

converts an overflowing space to an eol

buffer.text = 'one \n'
text.reflow_paragraph_at lines[1], 3
assert.equals 'one\n\n', buffer.text

does not modify the buffer unless there is a change

buffer.text = 'one two\n'
buffer.modified = false
text.reflow_paragraph_at lines[1], 10
assert.is_false buffer.modified
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/globals_spec.html b/site/source/versions/0.3/doc/spec/globals_spec.html deleted file mode 100644 index 84b08376b..000000000 --- a/site/source/versions/0.3/doc/spec/globals_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Globals - - - - - - - -
-
- -
- -

howl.Globals

callable <foo> returns true if foo can be invoked as a function

assert.is_true callable -> true
t = setmetatable {}, __call: -> true
assert.is_true callable t

typeof(v) is like type(), but handles regexes and moonscript classes

assert.equal 'regex', typeof r'foo'

class Bar
assert.equal 'Bar', typeof Bar!

r is a short alias for regex

assert.equal r, require 'howl.regex'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/interact_spec.html b/site/source/versions/0.3/doc/spec/interact_spec.html deleted file mode 100644 index ca3ab0462..000000000 --- a/site/source/versions/0.3/doc/spec/interact_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.interact - - - - - - - -
-
- -
- -

howl.interact

run_in_coroutine = (f) ->
  wrapped = coroutine.wrap -> f!
  return wrapped!

before_each ->
  if howl.app.window
    howl.app.window.command_line\abort_all!
  howl.app.window = Window!

after_each ->
  howl.app.window.command_line\abort_all!
  howl.app.window = nil

.unregister(name) removes the specified input

interact.register name: 'foo', description: 'foo', factory: -> true
interact.unregister 'foo'
assert.is_nil interact.foo

.register(spec)

raises an error if any of the mandatory fields are missing

assert.raises 'name', -> interact.register description: 'foo', factory: -> true
assert.raises 'description', -> interact.register name: 'foo', factory: -> true
assert.raises 'factory', -> interact.register name: 'foo', description: 'foo'

accepts one of "factory" or "handler"

assert.raises 'factory', -> interact.register
  name: 'foo'
  description: 'foo'
  factory: -> true
  handler: -> true

(calling an interaction .<name>(...))

before_each ->
  interact.register
    name: 'interaction_call'
    description: 'calls passed in function'
    handler: (f) -> f!

  interaction_instance =
    run: (@finish, f) => f(finish)

  interact.register
    name: 'interaction_with_factory',
    description: 'calls passed in function f(finish)'
    factory: -> moon.copy interaction_instance

after_each ->
  interact.unregister 'interaction_call'
  interact.unregister 'interaction_with_factory'

(.. for a spec with .handler)

local i1_spec
before_each ->
  i1_spec =
    name: 'interaction1'
    description: 'interaction with handler'
    handler: spy.new -> return 'r1', 'r2'
  interact.register i1_spec

after_each ->
  interact.unregister i1_spec.name

calls the interaction handler(...), returns result

multi_value = table.pack interact.interaction1 'arg1', 'arg2'
assert.spy(i1_spec.handler).was_called_with 'arg1', 'arg2'
assert.is_same {'r1', 'r2', n:2}, multi_value

(.. for a spec with .factory)

local i2_spec, i2_interactor
before_each ->
  i2_interactor =
    run: spy.new (@finish, ...) => return
  i2_spec =
    name: 'interaction2'
    description: 'interaction with factory'
    factory: -> i2_interactor
  interact.register i2_spec

after_each ->
  interact.unregister i2_spec.name

.<name>(...) invokes the interaction method run(finish, ...)

run_in_coroutine -> table.pack interact.interaction2 'arg1', 'arg2'
assert.spy(i2_interactor.run).was_called 1

.<name>(...) returns results passed via finish(...)

multi_value = nil
run_in_coroutine -> multi_value = table.pack interact.interaction2!
i2_interactor.finish 'r1', 'r2'
assert.is_same {'r1', 'r2', n:2}, multi_value

(.. nested transactions)

raises an error when attempting to finishing not active interaction

local captured_finish
capture_finish = (finish) -> captured_finish = finish

run_in_coroutine -> interact.interaction_with_factory capture_finish
finish1 = captured_finish
finish1!

assert.has_error finish1, 'Cannot finish - no running activities'

allows cancelling outer interactions, when nested interactions present

local captured_finish
capture_finish = (finish) -> captured_finish = finish

run_in_coroutine -> interact.interaction_with_factory capture_finish
run_in_coroutine -> interact.interaction_with_factory capture_finish
finish2 = captured_finish

assert.has_no_error finish2
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/interactions/buffer_selection_spec.html b/site/source/versions/0.3/doc/spec/interactions/buffer_selection_spec.html deleted file mode 100644 index c1791b324..000000000 --- a/site/source/versions/0.3/doc/spec/interactions/buffer_selection_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.interactions.buffer_selection - - - - - - - -
-
- -
- -

howl.interactions.buffer_selection

local command_line, editor
buffers = {}

before_each ->
  app.window = Window!
  app.window\realize!
  editor = {
    preview: (@buffer) => nil
  }
  command_line = app.window.command_line

  for b in *app.buffers
    app\close_buffer b

  for title in *{'a1-buffer', 'b-buffer', 'c-buffer', 'a2-buffer'}
    b = app\new_buffer!
    b.title = title
    table.insert buffers, b

normalize_titles = (titles) -> [t\gsub('<.*>', '') for t in *titles]

registers interactions

assert.not_nil interact.select_buffer

interact.select_buffer

displays a list of active buffers

local buflist
within_activity (-> interact.select_buffer :editor), ->
  buflist = get_ui_list_widget_column!
assert.same {'a1-buffer', 'a2-buffer', 'b-buffer', 'c-buffer'}, normalize_titles buflist

filters the buffer list based on entered text

local buflist
within_activity (-> interact.select_buffer :editor), ->
  command_line\write 'a-b'
  buflist = get_ui_list_widget_column!
assert.same {'a1-buffer', 'a2-buffer'}, normalize_titles buflist

previews currently selected buffer in the editor

previews = {}
down_event = {
  key_code: 65364
  key_name: 'down'
  alt: false
  control: false
  meta: false
  shift: false
  super: false
}

within_activity (-> interact.select_buffer :editor), ->
  table.insert previews, editor.buffer.title
  command_line\handle_keypress down_event
  table.insert previews, editor.buffer.title
assert.same {'a1-buffer', 'a2-buffer'}, normalize_titles previews

(sending binding_for("close"))

keymap = ctrl_w: 'close'
before_each -> bindings.push keymap
after_each -> bindings.remove keymap

close_event = {
  alt: false,
  character: "w",
  control: true,
  key_code: 119,
  key_name: "w",
  meta: false,
  shift: false,
  super: false
}

closes selected buffer

local buflist
within_activity (-> interact.select_buffer :editor), ->
  command_line\handle_keypress close_event
  command_line\handle_keypress close_event
  buflist = get_ui_list_widget_column!
assert.same {'b-buffer', 'c-buffer'}, normalize_titles buflist

preserves filter

local buflist
within_activity (-> interact.select_buffer :editor), ->
  command_line\write 'a-b'
  command_line\handle_keypress close_event
  buflist = get_ui_list_widget_column!
assert.same {'a2-buffer'}, normalize_titles buflist
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/interactions/file_selection_spec.html b/site/source/versions/0.3/doc/spec/interactions/file_selection_spec.html deleted file mode 100644 index d98489f1b..000000000 --- a/site/source/versions/0.3/doc/spec/interactions/file_selection_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.interactions.file_selection - - - - - - - -
-
- -
- -

howl.interactions.file_selection

local tmpdir, command_line

before_each ->
  for buf in *app.buffers
    app\close_buffer buf

  app.window = Window!
  app.window\realize!
  app.editor = app\new_editor!
  command_line = app.window.command_line
  tmpdir = File.tmpdir!

after_each ->
  tmpdir\rm_r!
  for buf in *app.buffers
    app\close_buffer buf
  app.editor = nil
  app.window\destroy!
  app.window = nil

registers interactions

assert.not_nil interact.select_file
assert.not_nil interact.select_file_in_project
assert.not_nil interact.select_directory

interact.select_file

opens the home directory by default

local prompt
within_activity interact.select_file, ->
  prompt = command_line.prompt
assert.same '~/', prompt

typing a path opens the closest parent

prompts = {}
within_activity interact.select_file, ->
  command_line\write tostring(tmpdir)
  table.insert prompts, command_line.prompt
assert.same {tostring(tmpdir.parent) .. '/'}, prompts

typing "/" after a directory name opens the directory

local prompt
within_activity interact.select_file, ->
  command_line\write tostring(tmpdir) .. '/'
  prompt = command_line.prompt
assert.same tostring(tmpdir) .. '/', prompt

typing "../" switches to the parent of the current directory

prompts = {}
within_activity interact.select_file, ->
  command_line\write tostring(tmpdir) .. '/'
  table.insert prompts, command_line.prompt
  command_line\write tostring(tmpdir) .. '../'
  table.insert prompts, command_line.prompt
assert.same {tostring(tmpdir) .. '/', tostring(tmpdir.parent) .. '/'}, prompts

typing "/" without any preceeding text changes to home directory

local prompt
within_activity interact.select_file, ->
  command_line\write '/'
  prompt = command_line.prompt
assert.same '/', prompt

shows files matching entered text in the current directory

files = { 'ab1', 'ab2', 'bc1' }
for f in *files
  f = tmpdir / f
  f.contents = 'a'

local items, items2
within_activity interact.select_file, ->
  command_line\write tostring(tmpdir) .. '/'
  items = get_ui_list_widget_column(1)

  command_line\write 'ab'
  items2 = get_ui_list_widget_column(1)

assert.same files, items
assert.same {'ab1', 'ab2'}, items2

(when a buffer associated with a file is open)

local buf

opens the directory of the current buffer, if any

buf, app.editor = app\open_file tmpdir / 'f'
local prompt
within_activity interact.select_file, ->
  prompt = command_line.prompt
assert.same tostring(tmpdir)..'/', prompt

(spillover)

(.. when spillover is not an absolute path)

opens the home directory and matches the spillover text

local prompt, text
command_line\write_spillover 'matchthis'
within_activity interact.select_file, ->
  prompt = command_line.prompt
  text = command_line.text
assert.same '~/', prompt
assert.same 'matchthis', text

(.. when spillover is an absolute path)

opens the closest valid directory

local prompt, text
command_line\write_spillover tostring(tmpdir / 'matchthis')
within_activity interact.select_file, ->
  prompt = command_line.prompt
  text = command_line.text
assert.same tostring(tmpdir)..'/', prompt
assert.same 'matchthis', text

(.. when spillover is a directory path that exists)

before_each ->
  File.mkdir tmpdir / 'subdir'

opens the directory when specified with a trailing "/"

local prompt, text
command_line\write_spillover tostring(tmpdir / 'subdir') .. '/'
within_activity interact.select_file, ->
  prompt = command_line.prompt
  text = command_line.text
assert.same tostring(tmpdir / 'subdir')..'/', prompt
assert.same '', text

opens the parent when specified without any trailing "/"

local prompt, text
command_line\write_spillover tostring(tmpdir / 'subdir')
within_activity interact.select_file, ->
  prompt = command_line.prompt
  text = command_line.text
assert.same tostring(tmpdir)..'/', prompt
assert.same 'subdir', text

(when config.hidden_file_extensions is set)

local files

before_each ->
  config.reset!
  config.hidden_file_extensions = {'a'}
  files = { 'x.a', 'x.b', 'x.c' }
  for f in *files
    f = tmpdir / f
    f.contents = 'x'

does not show hidden files in list

local items
within_activity interact.select_file, ->
  command_line\write tostring(tmpdir) .. '/'
  items = get_ui_list_widget_column!
assert.same { 'x.b', 'x.c' }, items

shows a hidden file after its exact name is entered

local items
within_activity interact.select_file, ->
  command_line\write tostring(tmpdir) .. '/'
  command_line\write 'x.a'
  command_line\clear!
  command_line\write ''
  items = get_ui_list_widget_column!
assert.same { 'x.b', 'x.c', 'x.a' }, items

interact.select_directory

shows only sub directories including "./", but no files

files = { 'ab1', 'ab2', 'bc1' }
directories = { 'dir1', 'dir2' }
for f in *files
  f = tmpdir / f
  f.contents = 'a'

for d in *directories
  f = tmpdir / d
  f\mkdir!

local items
within_activity interact.select_directory, ->
  command_line\write tostring(tmpdir) .. '/'
  items = get_ui_list_widget_column(1)

assert.same { './', 'dir1/', 'dir2/' }, items
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/interactions/line_selection_spec.html b/site/source/versions/0.3/doc/spec/interactions/line_selection_spec.html deleted file mode 100644 index c05e9702e..000000000 --- a/site/source/versions/0.3/doc/spec/interactions/line_selection_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.interactions.select_line - - - - - - - -
-
- -
- -

howl.interactions.select_line

local command_line, buffer, editor

before_each ->
  app.window = Window!
  app.window\realize!
  command_line = app.window.command_line

  buffer = Buffer!
  buffer.text = 'one\ntwo\nthree'
  editor = Editor buffer

registers interactions

assert.not_nil interact.select_line

interact.select_line

shows opt.lines in the completion list by default

local lines
within_activity (-> interact.select_line(:editor, lines: buffer.lines)), ->
  lines = get_ui_list_widget_column 2
assert.same {'one', 'two', 'three'}, lines

filters lines to match text entered

lines = {}
within_activity (-> interact.select_line(:editor, lines: buffer.lines)), ->
  append lines, get_ui_list_widget_column 2
  command_line\write 'o'
  append lines, get_ui_list_widget_column 2
  command_line\write 'n'
  append lines, get_ui_list_widget_column 2
  command_line\clear!
  command_line\write ''
  append lines, get_ui_list_widget_column 2

assert.same {'one', 'two', 'three'}, lines[1]
assert.same {'one', 'two'}, lines[2]
assert.same {'one'}, lines[3]
assert.same {'one', 'two', 'three'}, lines[4]
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/interactions/search_spec.html b/site/source/versions/0.3/doc/spec/interactions/search_spec.html deleted file mode 100644 index 340910186..000000000 --- a/site/source/versions/0.3/doc/spec/interactions/search_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.interactions.search - - - - - - - -
-
- -
- -

howl.interactions.search

local command_line, buffer, editor, searcher

before_each ->
  app.window = Window!
  app.window\realize!
  command_line = app.window.command_line

  searcher = {}
  app.editor = :searcher

registers interactions

assert.not_nil interact.forward_search
assert.not_nil interact.backward_search
assert.not_nil interact.forward_search_word
assert.not_nil interact.backward_search_word

searches forward for typed text

searcher.forward_to = spy.new -> true
within_activity interact.forward_search, ->
  command_line\write 'tw'
assert.spy(searcher.forward_to).was_called_with searcher, 'tw', 'plain'

interact.forward_search_word

searches forward for typed word

searcher.forward_to = spy.new -> true
within_activity interact.forward_search_word, ->
  command_line\write 'tw'
assert.spy(searcher.forward_to).was_called_with searcher, 'tw', 'word'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/io/file_spec.html b/site/source/versions/0.3/doc/spec/io/file_spec.html deleted file mode 100644 index c11149560..000000000 --- a/site/source/versions/0.3/doc/spec/io/file_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.io.File - - - - - - - -
-
- -
- -

howl.io.File

.basename returns the basename of the path

assert.equal 'base.ext', File('/foo/base.ext').basename

.extension returns the extension of the path

assert.equal File('/foo/base.ext').extension, 'ext'

.path returns the path of the file

assert.equal '/foo/base.ext', File('/foo/base.ext').path

.uri returns an URI representing the path

assert.equal File('/foo.txt').uri, 'file:///foo.txt'

.exists returns true if the path exists

File.with_tmpfile (file) -> assert.is_true file.exists

.parent return the parent of the file

assert.equal File('/bin/ls').parent.path, '/bin'

.children returns a table of children

with_tmpdir (dir) ->
  dir\join('child1')\mkdir!
  dir\join('child2')\touch!
  kids = dir.children
  table.sort kids, (a,b) -> a.path < b.path
  assert.same [v.basename for v in *kids], { 'child1', 'child2' }

.file_type is a string describing the file type

assert.equal 'directory', File('/bin').file_type
assert.equal 'regular', File('/bin/ls').file_type
assert.equal 'special', File('/dev/null').file_type

.writeable is true if the file represents a entry that can be written to

with_tmpdir (dir) ->
  assert.is_true dir.writeable
  file = dir / 'file.txt'
  assert.is_true file.writeable
  file\touch!
  assert.is_true file.writeable

assert.is_false File('/no/such/directory/orfile.txt').writeable

.readable is true if the file represents a entry that can be read

with_tmpdir (dir) ->
  assert.is_true dir.readable
  file = dir / 'file.txt'
  assert.is_false file.readable
  file\touch!
  assert.is_true file.readable

.etag is a string that can be used to check for modification

File.with_tmpfile (file) ->
  assert.is.not_nil file.etag
  assert.equal type(file.etag), 'string'

.modified_at is a the unix time when the file was last modified

File.with_tmpfile (file) ->
  assert.is.not_nil file.modified_at

read(..) is a short hand for doing a read(..) on the Lua file handle

File.with_tmpfile (file) ->
  file.contents = 'first line\n'
  assert.same { 'first', ' line' }, { file\read 5, '*l' }

join() returns a new file representing the specified child

assert.equal File('/bin')\join('ls').path, '/bin/ls'

relative_to_parent() returns a path relative to the specified parent

parent = File '/bin'
file = File '/bin/ls'
assert.equal 'ls', file\relative_to_parent(parent)

is_below(dir) returns true if the file is located beneath <dir>

parent = File '/bin'
assert.is_true File('/bin/ls')\is_below parent
assert.is_true File('/bin/sub/ls')\is_below parent
assert.is_false File('/usr/bin/ls')\is_below parent
assert.equal File.rm, File.delete
assert.equal File.unlink, File.delete

rm_r is an alias for delete_all

assert.equal File.rm_r, File.delete_all

tmpfile()

returns a file instance pointing to an existing file

file = File.tmpfile!
assert.is_true file.exists
file\delete!

with_tmpfile(f)

invokes <f> with the file

f = spy.new (file) ->
  assert.equals 'File', typeof(file)

File.with_tmpfile f
assert.spy(f).was_called(1)

removes the temporary file even if <f> raises an error

local tmpfile
f = (file) ->
  tmpfile = file
  error 'noo'

assert.raises 'noo', -> File.with_tmpfile f
assert.is_false tmpfile.exists

tmpdir()

returns a file instance pointing to an existing directory

file = File.tmpdir!
assert.is_true file.exists
assert.is_true file.is_directory
file\delete_all!

expand_path(path)

expands "~" into the full path of the home directory

assert.equals "#{os.getenv('HOME')}/foo.txt", (File.expand_path '~/foo.txt')

new(p, cwd)

accepts a string as denothing a path

File '/bin/ls'

accepts other files as well

f = File '/bin/ls'
f2 = File f
assert.equal f, f2

(when <cwd> is specified)

resolves a string <p> relative to <cwd>

assert.equal '/bin/ls', File('ls', '/bin').path

resolves an absolute string <p> as the absolute path

assert.equal '/bin/ls', File('/bin/ls', '/home').path

accepts other Files as <cwd>

assert.equal '/bin/ls', File('ls', File('/bin')).path

.is_absolute

returns true if the given path is absolute

assert.is_true File.is_absolute '/bin/ls'
assert.is_true File.is_absolute 'c:\\\\bin\\ls'

returns false if the given path is absolute

assert.is_false File.is_absolute 'bin/ls'
assert.is_false File.is_absolute 'bin\\ls'

.display_name

is the same as the basename for files

assert.equal 'base.ext', File('/foo/base.ext').display_name

has a trailing separator for directories

assert.equal 'bin/', File('/usr/bin').display_name

.short_path

returns the path with the home directory replace by "~"

file = File(os.getenv('HOME')) / 'foo.txt'
assert.equal '~/foo.txt', file.short_path

contents

assigning a string writes the string to the file

File.with_tmpfile (file) ->
  file.contents = 'hello world'
  f = io.open file.path
  read_back = f\read '*all'
  f\close!
  assert.equal read_back, 'hello world'

returns the contents of the file

File.with_tmpfile (file) ->
  f = io.open file.path, 'w'
  f\write 'hello world'
  f\close!
  assert.equal file.contents, 'hello world'

open([mode, function])

(when <function> is nil)

returns a Lua file handle

File.with_tmpfile (file) ->
  file.contents = 'first line\nsecond line\n'
  fh = file\open!
  assert.equal 'first line', fh\read!
  assert.equal 'second line\n', fh\read '*L'
  fh\close!

(when <function> is provided)

it is invoked with the file handle

File.with_tmpfile (file) ->
  file.contents = 'first line\nsecond line\n'
  local first_line
  file\open 'r', (fh) ->
    first_line = fh\read!

  assert.equal 'first line', first_line

returns the returns values of the function

File.with_tmpfile (file) ->
  assert.same { 'callback', nil, 'last' }, { file\open 'r', -> 'callback', nil, 'last' }

closes the file automatically after invoking <function>

File.with_tmpfile (file) ->
  local handle
  file\open 'r', (fh) -> handle = fh
  assert.has_errors -> handle\read!

(.. when <function> raises an error)

propagates that error

File.with_tmpfile (file) ->
  assert.raises 'kaboom', -> file\open 'r', -> error 'kaboom'

still closes the file

File.with_tmpfile (file) ->
  local handle
  pcall -> file\open 'r', (fh) ->
    handle = fh
    error 'kaboom'

  assert.has_errors -> handle\read!

mkdir()

creates a directory for the path specified by the file

File.with_tmpfile (file) ->
  file\delete!
  file\mkdir!
  assert.is_true file.exists and file.is_directory

raises an error if the directory could not be created

assert.has_error -> File('/aksdjskjdgudfkj')\mkdir!

mkdir_p()

creates a directory for the path specified by the file, including parents

File.with_tmpfile (file) ->
  file\delete!
  file = file\join 'sub/foo'
  file\mkdir_p!
  assert.is_true file.exists and file.is_directory

delete()

deletes the target file

File.with_tmpfile (file) ->
  file\delete!
  assert.is_false file.exists

raise an error if the file does not exist

file = File.tmpfile!
file\delete!
assert.error -> file\delete!

delete_all()

raise an error if the file does not exist

File.with_tmpfile (file) ->
  file\delete!
  assert.error -> file\delete!

(for a regular file)

deletes the target file

File.with_tmpfile (file) ->
  file\delete_all!
  assert.is_false file.exists

(for a directory)

deletes the directory and all sub entries

with_tmpdir (dir) ->
  dir\join('child1')\mkdir!
  dir\join('child1/sub_child')\touch!
  dir\join('child2')\touch!
  dir\delete_all!
  assert.is_false dir.exists

touch()

creates the file if does not exist

File.with_tmpfile (file) ->
  file\delete!
  file\touch!
  assert.is_true file.exists

raises an error if the file could not be created

file = File '/no/does/not/exist'
assert.error -> file\touch!

tostring()

returns a string containing the path

File.with_tmpfile (file) ->
  to_s = file\tostring!
  assert.equal 'string', typeof to_s
  assert.equal to_s, file.path

find()

with_populated_dir = (f) ->
  with_tmpdir (dir) ->
    dir\join('child1')\mkdir!
    dir\join('child1/sub_dir')\mkdir!
    dir\join('child1/sub_dir/deep.lua')\touch!
    dir\join('child1/sub_child.txt')\touch!
    dir\join('child1/sandwich.lua')\touch!
    dir\join('child2')\touch!
    f dir

raises an error if the file is not a directory

file = File '/no/does/not/exist'
assert.error -> file\find!

(with no parameters given)

returns a list of all sub entries

with_populated_dir (dir) ->
  files = dir\find!
  table.sort files, (a,b) -> a.path < b.path
  normalized = [f\relative_to_parent dir for f in *files]
  assert.same {
    'child1',
    'child1/sandwich.lua',
    'child1/sub_child.txt',
    'child1/sub_dir',
    'child1/sub_dir/deep.lua',
    'child2'
  }, normalized

(when the sort parameter is given)

returns a list of all sub entries in a pleasing order

with_populated_dir (dir) ->
  files = dir\find sort: true
  normalized = [f\relative_to_parent dir for f in *files]
  assert.same normalized, {
    'child2',
    'child1',
    'child1/sandwich.lua',
    'child1/sub_child.txt',
    'child1/sub_dir',
    'child1/sub_dir/deep.lua',
  }

(when filter: is passed as an option)

excludes files for which <filter(file)> returns true

with_populated_dir (dir) ->
  files = dir\find filter: (file) ->
    file.basename != 'sandwich.lua' and file.basename != 'child1'

  assert.same { 'child1', 'sandwich.lua' }, [f.basename for f in *files]

meta methods

/ and .. joins the file with the specified argument

file = File('/bin')
assert.equal (file / 'ls').path, '/bin/ls'
assert.equal (file .. 'ls').path, '/bin/ls'

tostring returns the result of File.tostring

file = File '/bin/ls'
assert.equal file\tostring!, tostring file

== returns true if the files point to the same path

assert.equal File('/bin/ls'), File('/bin/ls')
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/io/input_stream_spec.html b/site/source/versions/0.3/doc/spec/io/input_stream_spec.html deleted file mode 100644 index fad3373e7..000000000 --- a/site/source/versions/0.3/doc/spec/io/input_stream_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.io.InputStream - - - - - - - -
-
- -
- -

howl.io.InputStream

with_stream_for = (contents, cb) ->
  howl_async ->
    File.with_tmpfile (f) ->
      f.contents = contents
      cb InputStream GFile(f.path)\read!

read(num)

it 'reads up to <num> bytes from the stream', (done) ->

with_stream_for 'foobar', (stream) ->
  assert.equal 'foo', stream\read 3
  assert.equal 'bar', stream\read 10
  assert.is_nil stream\read 10
  done!

read_async(num, handler)

it 'invokes <handler> with the status and up to <num> bytes read from the stream', (done) ->

with_stream_for 'foobar', (stream) ->
  handler = (status, read) ->
    assert.is_true status
    assert.equal 'foobar', read
    done!

  stream\read_async 10, handler

it 'invokes <handler> with true and nil upon EOF', (done) ->

with_stream_for 'foobar', (stream) ->
  handler = (status, read) ->
    assert.is_true status
    assert.is_nil read
    done!

  stream\read 10
  stream\read_async 10, handler

read_all()

it 'reads all the streams content in one go', (done) ->

content = string.rep 'This is my line of text. Rinse, wash and repeat', 500, '\n'
with_stream_for content, (stream) ->
  read = stream\read_all!
  assert.equal #content, #read
  assert.equal content, read
  assert.is_nil stream\read 10
  done!

close

it 'closes the stream', (done) ->

with_stream_for 'foobar', (stream) ->
  assert.is_false stream.is_closed
  stream\close!
  assert.is_true stream.is_closed
  done!
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/io/process_spec.html b/site/source/versions/0.3/doc/spec/io/process_spec.html deleted file mode 100644 index c67f452cf..000000000 --- a/site/source/versions/0.3/doc/spec/io/process_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.io.Process - - - - - - - -
-
- -
- -

howl.io.Process

run = (...) ->
  with Process cmd: ...
    \wait!

Process(opts)

raises an error if opts.cmd is missing or invalid

assert.raises 'cmd', -> Process {}
assert.raises 'cmd', -> Process cmd: 2
assert.not_error -> Process cmd: 'id'
assert.not_error -> Process cmd: {'echo', 'foo'}

returns a process object

assert.equal 'Process', typeof Process cmd: 'true'

raises an error for an unknown command

assert.raises 'howlblargh', -> Process cmd: {'howlblargh'}

sets .argv to the parsed command line

p = Process cmd: {'echo', 'foo'}
assert.same {'echo', 'foo'}, p.argv

p = Process cmd: 'echo "foo bar"'
assert.same { '/bin/sh', '-c', 'echo "foo bar"'}, p.argv

allows specifying a different shell

p = Process cmd: 'foo', shell: '/bin/echo'
assert.same { '/bin/echo', '-c', 'foo'}, p.argv

Process.execute(cmd, opts)

it 'executes the specified command and return <out, err, process>', (done) ->

howl_async ->
  out, err, p = Process.execute {'sh', '-c', 'cat; echo foo >&2'}, stdin: 'reverb'
  assert.equal 'reverb', out
  assert.equal 'foo\n', err
  assert.equal 'Process', typeof(p)
  done!

it "executes string commands using /bin/sh by default", (done) ->

howl_async ->
  status, out = pcall Process.execute, 'echo $0'
  assert.is_true status
  assert.equal '/bin/sh\n', out
  done!

it "allows specifying a different shell", (done) ->

howl_async ->
  status, out, err, process = pcall Process.execute, 'blargh', shell: '/bin/echo'
  assert.is_true status
  assert.match out, 'blargh'
  assert.equal 'blargh', process.command_line
  done!

it 'opts.working_directory sets the working working directory', (done) ->

howl_async ->
  with_tmpdir (dir) ->
    out = Process.execute 'pwd', working_directory: dir
    assert.equal dir.path, out.stripped
    done!

it 'opts.env sets the process environment', (done) ->

howl_async ->
  out = Process.execute {'env'}, env: { foo: 'bar' }
  assert.equal 'foo=bar', out.stripped
  done!

it 'works with large process outputs', (done) ->

howl_async ->
  File.with_tmpfile (f) ->
    file_contents = string.rep "xxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyy zzzzzzzzzzzzzzzzzzz\n", 5000
    f.contents = file_contents
    status, out = pcall Process.execute, "cat #{f.path}"
    assert.is_true status
    assert.equal file_contents, out
    done!

pump(on_stdout, on_stderr)

(when the <on_stdout> handler is provided)

it 'invokes the handler for any stdout output before returning', (done) ->

howl_async ->
  on_stdout = spy.new -> nil
  p = Process cmd: 'echo foo', read_stdout: true
  p\pump on_stdout
  assert.is_true p.exited
  assert.spy(on_stdout).was_called_with 'foo\n'
  assert.spy(on_stdout).was_called_with nil
  done!

(when the <on_stderr> handler is provided)

it 'invokes the handler for any stderr output before returning', (done) ->

howl_async ->
  on_stderr = spy.new -> nil
  p = Process cmd: 'echo err >&2', read_stderr: true
  p\pump nil, on_stderr
  assert.is_true p.exited
  assert.spy(on_stderr).was_called_with 'err\n'
  assert.spy(on_stderr).was_called_with nil
  done!

(when both handlers are provided)

it 'invokes both handlers for any output before returning', (done) ->

howl_async ->
  on_stdout = spy.new -> nil
  on_stderr = spy.new -> nil
  p = Process cmd: 'echo out; echo err >&2', read_stdout: true, read_stderr: true
  p\pump on_stdout, on_stderr
  assert.is_true p.exited
  assert.spy(on_stdout).was_called_with 'out\n'
  assert.spy(on_stdout).was_called_with nil
  assert.spy(on_stderr).was_called_with 'err\n'
  assert.spy(on_stderr).was_called_with nil
  done!

wait()

it 'waits until the process is finished', (done) ->

settimeout 2
howl_async ->
  File.with_tmpfile (file) ->
    file\delete!
    p = Process cmd: { 'sh', '-c', "sleep 1; touch '#{file.path}'" }
    p\wait!
    assert.is_true file.exists
    done!

(signal handling)

send_signal(signal) and .signalled

it 'sends the specified signal to the process', (done) ->

howl_async ->
  p = Process cmd: 'cat', write_stdin: true
  p\send_signal 9
  p\wait!
  assert.is_true p.signalled
  done!

it '.signalled is false for a non-signaled process', (done) ->

howl_async ->
  p = Process cmd: 'id'
  p\wait!
  assert.is_false p.signalled
  done!

it '.signal holds the signal used for terminating the process', (done) ->

howl_async ->
  p = Process cmd: 'cat', write_stdin: true
  p\send_signal 9
  p\wait!
  assert.equals 9, p.signal
  done!

it '.signal_name holds the name of the signal used for terminating the process', (done) ->

howl_async ->
  p = Process cmd: 'cat', write_stdin: true
  p\send_signal 9
  p\wait!
  assert.equals 'KILL', p.signal_name
  done!

it 'signals can be referred to by name as well', (done) ->

howl_async ->
  p = Process cmd: 'cat', write_stdin: true
  p\send_signal 'KILL'
  p\wait!
  assert.equals 9, p.signal
  done!

.exit_status

is nil for a running process

p = Process cmd: { 'sh', '-c', "sleep 1; true" }
assert.is_nil p.exit_status
p\wait!

it 'is nil for a signalled process', (done) ->

howl_async ->
  p = Process cmd: 'cat', write_stdin: true
  p\send_signal 9
  p\wait!
  assert.is_nil p.exit_status
  done!

it 'is set to the exit status for a normally exited process', (done) ->

howl_async ->
  p = run 'echo foo'
  assert.equals 0, p.exit_status

  p = run {'sh', '-c', 'exit 1' }
  assert.equals 1, p.exit_status

  p = run {'sh', '-c', 'exit 2' }
  assert.equals 2, p.exit_status

  done!

.working_directory

(when provided during launch)

is the same directory

cwd = File '/bin'
p = Process(cmd: 'true', working_directory: cwd)
assert.equal cwd, p.working_directory

is always a File instance

p = Process(cmd: 'true', working_directory: '/bin')
assert.equal 'File', typeof  p.working_directory

(when not provided)

is the current working directory

p = Process(cmd: 'true')
assert.equal File(glib.get_current_dir!), p.working_directory

.successful

it 'is true if the process exited cleanly with a zero exit code', (done) ->

howl_async ->
  assert.is_true run('id').successful
  done!

it 'is false if the process exited with a non-zero exit code', (done) ->

howl_async ->
  assert.is_false run('false').successful
  done!

it 'is false if the process exited due to a signal', (done) ->

howl_async ->
  p = Process cmd: 'cat', write_stdin: true
  p\send_signal 9
  p\wait!
  assert.is_false p.successful
  done!

.stdout

it 'allows reading process output', (done) ->

howl_async ->
  p = Process cmd: {'echo', 'one\ntwo'}, read_stdout: true
  assert.equals 'one\ntwo\n', p.stdout\read!
  assert.is_nil p.stdout\read!
  done!

.stderr

it 'allows reading process error output', (done) ->

howl_async ->
  p = Process cmd: {'sh', '-c', 'echo foo >&2'}, read_stderr: true
  assert.equals 'foo\n', p.stderr\read!
  done!

.stdin

it 'allows writing to the process input', (done) ->

howl_async ->
  p = Process cmd: {'cat'}, write_stdin: true, read_stdout: true
  with p.stdin
    \write 'round-trip'
    \close!

  assert.equals 'round-trip', p.stdout\read!
  p\wait!
  done!

.command_line

(when the command is specified as a string)

is the same

assert.equal 'echo command "bar"', run('echo command "bar"').command_line

(when the command is specified as a table)

is a created shell command line

assert.equal "echo command 'bar zed'", run({'echo', 'command', 'bar zed'}).command_line

.exit_status_string

provides the exit code for a normally terminated process

assert.equals 'exited normally with code 0', run('id').exit_status_string
assert.equals 'exited normally with code 1', run('exit 1').exit_status_string

provides the signal name for a killed process

p = Process cmd: {'cat'}, write_stdin: true, read_stdout: true
p\send_signal 'KILL'
p\wait!
assert.equals 'killed by signal 9 (KILL)', p.exit_status_string

Process.running

is a table of currently running processes, keyed by pid

assert.same {}, Process.running
p = Process cmd: {'cat'}, write_stdin: true
assert.same {[p.pid]: p}, Process.running
p.stdin\close!
p\wait!
assert.same {}, Process.running
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/log_spec.html b/site/source/versions/0.3/doc/spec/log_spec.html deleted file mode 100644 index 597452afe..000000000 --- a/site/source/versions/0.3/doc/spec/log_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.log - - - - - - - -
-
- -
- -

howl.log

after_each ->
  log.clear!
  app.window = nil

is exported globally as `log`

assert.equal type(_G.log), 'table'

warn() is the same as warning()

assert.same log.warn, log.warning

(when howl.app.window is available and showing)

local method

before_each ->
  method = spy.new -> true
  app.window = visible: true, command_line: {}, status: [m]: method

after_each ->
  app.window = nil

sends the message to howl.app.window.status\\' .. m .. '() if available

log[m] 'message'
assert.spy(method).was.called_with app.window.status, 'message'

only propagates the first line of the message

log[m] 'message\nline2\nline3'
assert.spy(method).was.called_with app.window.status, 'message'

removes any location info before propagating

log[m] '[string "../foo/bar.lua"]:32: juicy bit'
assert.spy(method).was.called_with app.window.status, 'juicy bit'

(when howl.app.window is available and showing)

local method

before_each ->
  method = spy.new -> true
  app.window = visible: true, command_line: {}, status: [m]: method

after_each ->
  app.window = nil

sends the message to howl.app.window.status\\' .. m .. '() if available

log[m] 'message'
assert.spy(method).was.called_with app.window.status, 'message'

only propagates the first line of the message

log[m] 'message\nline2\nline3'
assert.spy(method).was.called_with app.window.status, 'message'

removes any location info before propagating

log[m] '[string "../foo/bar.lua"]:32: juicy bit'
assert.spy(method).was.called_with app.window.status, 'juicy bit'

(when howl.app.window is available and showing)

local method

before_each ->
  method = spy.new -> true
  app.window = visible: true, command_line: {}, status: [m]: method

after_each ->
  app.window = nil

sends the message to howl.app.window.status\\' .. m .. '() if available

log[m] 'message'
assert.spy(method).was.called_with app.window.status, 'message'

only propagates the first line of the message

log[m] 'message\nline2\nline3'
assert.spy(method).was.called_with app.window.status, 'message'

removes any location info before propagating

log[m] '[string "../foo/bar.lua"]:32: juicy bit'
assert.spy(method).was.called_with app.window.status, 'juicy bit'

book keeping

.entries is a list of the last log entries

log.error 'my error'
assert.equal #log.entries, 1
assert.same log.entries[1], {
  message: 'my error'
  level: 'error'
}

.last_error points to the last error logged

assert.is_nil log.last_error
log.error 'foo'
assert.equal 'foo', log.last_error.message
log.error 'bar'
assert.equal 'bar', log.last_error.message

defines a "max_log_entries" config variable, defaulting to 1000

assert.not_nil config.definitions.max_log_entries
assert.equal config.max_log_entries, 1000

retains at most <max_log_entries> of the last entries

config.max_log_entries = 1
for i = 1,10
  log.error 'my error ' .. i

assert.equal #log.entries, 1
assert.same log.entries[1], {
  message: 'my error 10'
  level: 'error'
}

.clear() clears all log entries

log.error 'my error'
log.clear!
assert.equal #log.entries, 0
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/mode_spec.html b/site/source/versions/0.3/doc/spec/mode_spec.html deleted file mode 100644 index ebe7703c1..000000000 --- a/site/source/versions/0.3/doc/spec/mode_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.mode - - - - - - - -
-
- -
- -

howl.mode

after_each ->
  for name in *[name for name in *mode.names when name != 'default' ]
    mode.unregister name

mode instances are memoized

mode.register name: 'same', extensions: 'again', create: -> {}
assert.equal mode.by_name('same'), mode.for_file File 'once.again'

mode instances automatically have their .name set

mode.register name: 'named', extensions: 'again', create: -> {}
assert.equal mode.by_name('named').name, 'named'

.names contains all registered mode names and their aliases

mode.register name: 'needle', aliases: 'foo', create: -> {}
assert.includes mode.names, 'needle'
assert.includes mode.names, 'foo'

.register(spec)

raises an error if any of the mandatory inputs are missing

assert.raises 'name', -> mode.register {}
assert.raises 'create', -> mode.register name: 'foo'

.by_name(name)

returns a mode instance for <name>, or nil if not existing

assert.is_nil mode.by_name 'blargh'

mode.register name: 'shish', create: -> {}
assert.is_not_nil mode.by_name('shish')

allows looking up modes by any aliases

assert.is_nil mode.by_name 'my_mode'

mode.register name: 'shish', aliases: {'my_mode'}, create: -> {}
assert.is_not_nil mode.by_name('my_mode')

for_extension(extension)

returns a mode registered for <extension>, if any

mode.register name: 'ext', extensions: 'foo', create: -> {}
assert.equal 'ext', mode.for_extension('foo').name

.for_file(file)

(when the file extension is registered with a mode)

returns an instance of that mode

mode.register name: 'ext', extensions: 'foo', create: -> {}
file = File 'test.foo'
assert.equal 'ext', mode.for_file(file).name

(when the file paths matches a mode pattern)

returns an instance of that mode

mode.register name: 'pattern', patterns: 'match%w+$', create: -> {}
file = File 'matchme'
assert.equal 'pattern', mode.for_file(file).name

(when the file header matches a mode shebang)

returns an instance of that mode

mode.register name: 'shebang', shebangs: 'lua$', create: -> {}
File.with_tmpfile (file) ->
  file.contents = '#! /usr/bin/lua\nother line\nand other\n'
  assert.equal 'shebang', mode.for_file(file).name

(when no matching mode can be found)

returns an instance of the mode "default"

file = File 'test.blargh'
assert.equal 'default', mode.for_file(file).name

(mode creation)

modes are created by calling the modes create function, passing the name

create = spy.new -> {}
mode.register name: 'callme', :create
mode.by_name('callme')
assert.spy(create).was_called_with 'callme'

mode configuration variables

config.define name: 'mode_var', description: 'some var', default: 'def value'

mode instances automatically have their .config set

mode.register name: 'config', create: -> {}
mode_config = mode.by_name('config').config
assert.is_not_nil mode_config

assert.equal 'def value', mode_config.mode_var
mode_config.mode_var = 123
assert.equal 123, mode_config.mode_var
assert.equal 'def value', config.mode_var

the .config is pre-seeded with variables from .default_config of the mode (if any)

mode.register name: 'pre_config', create: -> default_config: { mode_var: 543 }
assert.equal 543, mode.by_name('pre_config').config.mode_var

configure(mode_name, variables)

before_each ->
  mode.register name: 'user_configured', create: -> {}

allows setting mode specific variables automatically upon creation

mode.configure 'user_configured', mode_var: 'from_user'
assert.equal 'from_user', mode.by_name('user_configured').config.mode_var

automatically sets the config variables for any already instantiated mode

mode_config = mode.by_name('user_configured').config
mode.configure 'user_configured', mode_var: 'after_the_fact'
assert.equal 'after_the_fact', mode_config.mode_var

overrides any default mode configuration set

mode.register name: 'mode_with_config', create: -> default_config: { mode_var: 'mode set' }
mode.configure 'mode_with_config', mode_var: 'user set'
assert.equal 'user set', mode.by_name('mode_with_config').config.mode_var

mode inheritance

before_each ->
  base = foo: 'foo'
  mode.register name: 'base', create: -> base
  mode.register name: 'sub', parent: 'base', create: -> {}
  config.define name: 'delegated_mode_var', description: 'some var', default: 'def value'

the instantiated mode has .parent set to the instantiated parent

assert.equal mode.by_name('base'), mode.by_name('sub').parent

a mode extending another mode automatically delegates to that mode

assert.equal 'foo', mode.by_name('sub').foo
 mode.by_name('base').config.delegated_mode_var = 123
 assert.equal 123, mode.by_name('sub').config.delegated_mode_var

an error is raised if the mode indicated by parent does not exist

assert.has_error ->
  mode.register name: 'wrong', parent: 'keyser_soze', create: -> {}
  mode.by_name 'wrong'

parent defaults to "default" unless given

mode.register name: 'orphan', create: -> {}
assert.equal mode.by_name('default'), mode.by_name('orphan').parent

.unregister(name)

removes the mode specified by <name>

mode.register name: 'mode', aliases: 'foo', extensions: 'zen', create: -> {}
mode.unregister 'mode'
assert.is_nil mode.by_name 'mode'
assert.is_nil mode.by_name 'foo'
assert.equal mode.for_file(File('test.zen')), mode.by_name 'default'

removes any memoized instance

mode.register name: 'memo', extensions: 'memo', create: -> {}
mode.unregister 'memo'
live = mode.by_name 'memo'
mode.register name: 'memo', extensions: 'memo', create: -> {}
assert.is_not_equal live, mode.by_name('memo')
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/modes/default_mode_spec.html b/site/source/versions/0.3/doc/spec/modes/default_mode_spec.html deleted file mode 100644 index 5929cf5a8..000000000 --- a/site/source/versions/0.3/doc/spec/modes/default_mode_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.modes.DefaultMode - - - - - - - -
-
- -
- -

howl.modes.DefaultMode

local buffer, mode, lines, indentation
editor = Editor Buffer {}
cursor = editor.cursor
selection = editor.selection

indent = ->
  selection\select_all!
  mode\indent editor

before_each ->
  buffer = Buffer {}
  mode = DefaultMode!
  indentation = {}
  mode.indentation = indentation
  buffer.mode = mode
  buffer.config.indent = 2
  lines = buffer.lines
  editor.buffer = buffer

.indent()

does not try to indent lines within comments or strings

indentation.more_after = { '{' }
  indentation.less_for = { '}' }
  buffer = ActionBuffer!
  buffer.text = '{\nfoo\n  }\n'
  lines = buffer.lines
  buffer\style lines[1].start_pos, lines[2].end_pos, 'comment'
  buffer\style lines[3].start_pos, lines[4].end_pos, 'string'
  editor.buffer = buffer
  indent!
  assert.equals '{\nfoo\n  }\n', buffer.text

uses the same indent as for the previous line if it is a comment

indentation.more_after = { '{' }
  mode.comment_syntax = '#'
  buffer.text = "  # I'm commenting thank you very much {\n# and still are\n"
  indent!
  assert.equals 2, lines[2].indentation

adjust any illegal indentation (not divisable by indent)

buffer.text = '  line\n two\n'
indent!
assert.equals '  line\n  two\n', buffer.text

works on the current line if no selection is specified

indentation.more_after = { 'if' }
buffer.text = 'if\none\ntwo\n'
cursor.line = 2
mode\indent editor
assert.equals 'if\n  one\ntwo\n', buffer.text

moves the cursor to the beginning of indentation if it would be positioned before

buffer.text = '  line\n\n'
cursor.line = 2
mode\indent editor
assert.equals '  line\n  \n', buffer.text
assert.equals 3, cursor.column

(when .indentation.more_after patterns is set)

(.. and the previous line matches one of the patterns)

indents lines below matching lines with the currently set indent

indentation.more_after = { r'if', 'then' }
buffer.text = 'if\nbar\nthen\nfoo\n'
indent!
assert.equals 'if\n  bar\nthen\n  foo\n', buffer.text

(.. when .authoritive is not false)

adjusts lines with unwarranted greater indents to match the previous line

indentation.more_after = { 'if' }
buffer.text = 'line\n  wat?\n'
indent!
assert.equals 'line\nwat?\n', buffer.text

(.. when .authoritive is false)

does not adjust lines with unwarranted greater indents to match the previous line

indentation.more_after = { 'if', authoritive: false }
buffer.text = 'line\n  wat?\n'
indent!
assert.equals 'line\n  wat?\n', buffer.text

(when .indentation.less_for is set)

(.. and the current line matches one of the patterns)

dedents the line one level below the previous line if it exists

indentation.less_for = { r'else', '}' }
buffer.text = '    bar\n    else\n  foo\n  }\n'
indent!
assert.equals '    bar\n  else\n  foo\n}\n', buffer.text

(.. when .authoritive is not false)

adjusts lines with unwarranted smaller indents to match the previous line

indentation.less_for = { 'else' }
buffer.text = '  line\nwat?\n'
indent!
assert.equals '  line\n  wat?\n', buffer.text

(.. when .authoritive is false)

does not adjust lines with unwarranted smaller indents to match the previous line

indentation.less_for = { 'else', authoritive: false }
buffer.text = '  line\nwat?\n'
indent!
assert.equals '  line\nwat?\n', buffer.text

(when .indentation.more_for is set)

(.. and the current line matches one of the patterns)

indents the line one level right of the previous line if it exists

indentation.more_for = { '^.' }
buffer.text = 'bar\n.foo\n'
indent!
assert.equals 'bar\n  .foo\n', buffer.text

(when .indentation.same_after patterns is set)

(.. and the previous line matches one of the patterns)

indents lines below matching lines to have the same indent as the previous line

indentation.same_after = ',$'
buffer.text = '  foo,\nbar'
indent!
assert.equals '  foo,\n  bar', buffer.text

(when more than one of .less_for, .more_after or .same_after are set)

they are weighed together

indentation.more_after = { '{' }
indentation.less_for = { '}' }
indentation.same_after = { ',$' }
buffer.text = '  {\n  }'
indent!
assert.equals '  {\n  }', buffer.text

buffer.text = '  {\n  foo,}'
indent!
assert.equals '  {\n  foo,}', buffer.text

(when a line is blank)

does not indent unless it is the current line

indentation.more_after = { '{' }
  indentation.less_for = { '}' }
  buffer.text = '{\n\n}'
  indent!
  assert.equals '{\n\n}', buffer.text

(.. and it is the current line)

indents according to patterns

indentation.more_after = { '{' }
  indentation.less_for = { '}' }
  buffer.text = '{\n\n}'
  cursor.line = 2
  mode\indent editor
  assert.equals '{\n  \n}', buffer.text

sets the same indent as for the previous line if nothing else is specified

buffer.text = '  line\n\n'
cursor.line = 2
mode\indent editor
assert.equals '  line\n  \n', buffer.text

comment(editor)

text = [[
  liñe 1

liñe 2
liñe 3
  ]]
before_each ->
  buffer.text = text
  selection\set 1, lines[4].start_pos

(when .comment_syntax is not set)

does nothing

mode\comment editor
assert.equal text, buffer.text

(when .comment_syntax is set to a string)

before_each -> mode.comment_syntax = '--'

prefixes the selected lines with the prefix and a space, at the minimum indentation level

mode\comment editor
assert.equal [[

    liñe 3
  ]], buffer.text

comments the current line if nothing is selected

selection\remove!
cursor.pos = 1
mode\comment editor
assert.equal [[

    liñe 2
    liñe 3
  ]], buffer.text

keeps the cursor position

editor.selection.cursor = lines[3].start_pos + 2
mode\comment editor
assert.equal 6, cursor.column

(when .comment_syntax is set to a pair)

before_each -> mode.comment_syntax = {'/*', '*/'}

wraps each selected line with the pair, at the minimum indentation level

mode\comment editor
assert.equal [[
  /* liñe 1 */

  /*   liñe 2 */
    liñe 3
  ]], buffer.text

comments the current line if nothing is selected

selection\remove!
cursor.pos = 1
mode\comment editor
assert.equal [[
  /* liñe 1 */

    liñe 2
    liñe 3
  ]], buffer.text

keeps the cursor position

editor.selection.cursor = lines[3].start_pos + 2
mode\comment editor
assert.equal 6, cursor.column

uncomment(editor)

(when .comment_syntax is not set)

does nothing

buffer.text = 'foo\nbar\n'
selection\set 1, lines[2].start_pos
mode\uncomment editor
assert.equal 'foo\nbar\n', buffer.text

(when .comment_syntax is set to a string)

before_each ->
  buffer.mode.comment_syntax = '--'
  buffer.text = [[
]]
  selection\set 1, lines[3].start_pos

removes the first instance of the comment prefix and optional space from each line

mode\uncomment editor
assert.equal [[
   liñe 1
]], buffer.text

uncomments the current line if nothing is selected

selection\remove!
cursor.line = 2
mode\uncomment editor
assert.equal [[
]], buffer.text

keeps the cursor position

editor.selection.cursor = lines[2].start_pos + 6
mode\uncomment editor
assert.equal 4, cursor.column

does nothing for lines that are not commented

buffer.text = "line\n"
cursor.line = 1
mode\uncomment editor
assert.equal "line\n", buffer.text

(when .comment_syntax is set to a pair)

before_each ->
  buffer.mode.comment_syntax = {'/*', '*/'}
  buffer.text = [[
  /*  liñe 1 */
    /* liñe 2 */
    /*liñe 3*/
]]
  selection\set 1, lines[3].start_pos

removes the first instance of the comment prefix and optional space from each line

mode\uncomment editor
assert.equal [[
   liñe 1
    liñe 2
    /*liñe 3*/
]], buffer.text

uncomments the current line if nothing is selected

selection\remove!
cursor.line = 2
mode\uncomment editor
assert.equal [[
  /*  liñe 1 */
    liñe 2
    /*liñe 3*/
]], buffer.text

keeps the cursor position

editor.selection.cursor = lines[2].start_pos + 6
mode\uncomment editor
assert.equal 4, cursor.column

does nothing for lines that are not commented

buffer.text = "line\n"
cursor.line = 1
mode\uncomment editor
assert.equal "line\n", buffer.text

toggle_comment(editor)

(when mode does not provide .comment_syntax)

does nothing

buffer.text = '-- foo'
mode\toggle_comment editor
assert.equal '-- foo', buffer.text

(when mode provides .comment_syntax)

before_each -> buffer.mode.comment_syntax = '--'

it uncomments if the first line starts with the comment prefix

buffer.text = '  -- foo'
mode\toggle_comment editor
assert.equal '  foo', buffer.text

comments if the first line do no start with the comment prefix

buffer.text = 'foo'
mode\toggle_comment editor
assert.equal '-- foo', buffer.text

auto-formatting after newline

indents the new line automatically given the indent patterns

indentation.more_after = { 'if' }
buffer.text = 'if'
cursor\eof!
editor\newline!
assert.equals 'if\n  ', buffer.text

buffer.text = 'other'
cursor\eof!
editor\newline!
assert.equals 'other\n', buffer.text

structure()

assert_lines = (expected, actual) -> assert.same [l.nr for l in *expected], [l.nr for l in *actual]

returns a simple indentation-based structure

buffer.text = [[
  header1
    sub1
      foo
      bar
      zed
      froz
  header2
    sub2
]]
assert_lines {
  lines[1]
  lines[2]
  lines[7]
}, mode\structure editor

the config variable indentation_structure_threshold determines when it stops

buffer.text = [[
  header1
    sub1
      froz
  header2
    sub2
]]
buffer.config.indentation_structure_threshold = 2
assert_lines { lines[1], lines[4] }, mode\structure editor

disregards blank lines

buffer.text = '\n  sub\n'
assert.same {}, mode\structure editor

(if a structure line is all non-alpha)

tries to use the previous line if that contains alpha characters

buffer.text = [[
  int func(int arg)
  {
    froz();
  }
]]
assert_lines { lines[1] }, mode\structure editor

skips the line altogether if the previous line does not contain alpha characters

buffer.text = [[

  {
    froz();
  }
]]
assert_lines {}, mode\structure editor

patterns_match(text, patterns)

returns a boolean indicating whether <text> matches any of the specified patterns

assert.is_true mode\patterns_match 'foo', { 'foo' }
assert.is_false mode\patterns_match 'foo', { 'bar' }

accepts both Lua patterns and regexes

assert.is_true mode\patterns_match 'foo', { 'fo+' }
assert.is_true mode\patterns_match 'foo', { r'\\pLo*' }

a specifed pattern can be table containing both a positiv and a negative match

p = { 'foo', 'bar' }
assert.is_true mode\patterns_match 'foo zed', { p }
assert.is_false mode\patterns_match 'foo bar', { p }

(when a newline is added)

sets the indentation for the new line to the indentation of the previous non-blank line

buffer.text = '  line1\n\nline3'
cursor.line = 3
editor\newline!
assert.equals 2, editor.current_line.indentation

(.. when .code_blocks.multiline is present)

the code blocks are automatically enforced

mode.code_blocks = multiline: {
  { '%sdo$', '^%s*end', 'end' },
}
buffer.text = 'foo do'
cursor\eof!
editor\newline!
assert.equals 'foo do\n\nend\n', buffer.text
assert.equals 2, cursor.line

is ignored if the configuration variable "auto_format" is false

buffer.config.auto_format = false
mode.code_blocks = multiline: {
  { '%sdo$', '^%s*end', 'end' },
}
buffer.text = 'foo do'
cursor\eof!
editor\newline!
assert.equals 'foo do\n', buffer.text
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/project_spec.html b/site/source/versions/0.3/doc/spec/project_spec.html deleted file mode 100644 index 6d6d20637..000000000 --- a/site/source/versions/0.3/doc/spec/project_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Project - - - - - - - -
-
- -
- -

howl.Project

after_each ->
  Project.roots = {}
  Project.open = {}

.roots contains all known roots

assert.same {}, Project.roots
with_tmpdir (dir) ->
  Project.add_root dir
  assert.same {dir}, Project.roots

.add_root adds the given root if not already present

with_tmpdir (dir) ->
  Project.add_root dir
  Project.add_root dir
  assert.equal 1, #Project.roots

.remove_root removes the given root

with_tmpdir (dir) ->
  Project.add_root dir
  Project.remove_root dir
  assert.equal 0, #Project.roots

.for_file(file)

raises an error if file is nil

assert.raises 'file', -> Project.for_file nil

returns nil by default

File.with_tmpfile (file) ->
  assert.is_nil Project.for_file file

(when there is VC found for the file)

vc = name: 'vc', root: 'foo_root', files: -> {}
before_each -> VC.register 'vc', find: -> vc
after_each -> VC.unregister 'vc'

returns a project instantiated with the vc and vc root

p = Project.for_file 'file'
assert.not_nil p
assert.equal p.root, vc.root
assert.equal p.vc, vc

adds the new root to .roots

Project.for_file 'file'
assert.same Project.roots, {vc.root}

adds a new entry for the root and project to .open

p = Project.for_file 'file'
assert.same Project.open, { [vc.root]: p }

(when there is a known root containing the file)

returns a new project for the root

with_tmpdir (dir) ->
  Project.add_root dir
  file = dir / 'test.moon'
  p = Project.for_file file
  assert.not_nil p
  assert.equal p.root, dir

automatically sets the matching VC if possible

with_tmpdir (dir) ->
  Project.add_root dir
  file = dir / 'test.moon'
  vc = name: 'vc', root: dir, files: -> {}
  VC.register 'vc', find: (file) -> return vc if file == file
  p = Project.for_file file
  VC.unregister 'vc'
  assert.equal p.vc, vc

(when there is an open project containing the file)

returns the existing project

with_tmpdir (dir) ->
  Project.add_root dir
  file = dir / 'test.moon'
  file2 = dir / 'test2.moon'
  p = Project.for_file file
  p2 = Project.for_file file
  assert.not_nil p
  assert.equal p2, p

for a given project instance

.files()

delegates to .vc.files() if it is available

vc = files: -> 'files'
assert.equal vc.files!, Project('root', vc)\files!

falls back to a FS scan, skipping directories and hidden and backup files

with_tmpdir (dir) ->
  regular = dir / 'regular.lua'
  regular\touch!
  sub_dir = dir / 'sub_dir'
  sub_dir\mkdir!
  hidden = dir / '.config'
  hidden\touch!
  backup = dir / 'config~'
  backup\touch!
  assert.same { regular.path }, [f.path for f in *Project(dir)\files!]
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/regex_spec.html b/site/source/versions/0.3/doc/spec/regex_spec.html deleted file mode 100644 index 89a840490..000000000 --- a/site/source/versions/0.3/doc/spec/regex_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Regex - - - - - - - -
-
- -
- -

howl.Regex

.pattern holds the regex used for construction

assert.equal 'foo(bar)', r('foo(bar)').pattern

.capture_count holds the number of captures in the pattern

assert.equal 2, r('foo(bar) (\\w+)').capture_count

r.is_instance(v) returns true if <v> is a regex

assert.is_true r.is_instance r'foo'
assert.is_false r.is_instance 'foo'
assert.is_false r.is_instance {}

escape(s) returns a string with all special regular expression symbols escaped

assert.equal 'a\\.b\\*c', r.escape 'a.b*c'

tostring(regex) returns the pattern

assert.equal '\\s*(foo)', tostring r'\\s*(foo)'

(creation)

raises an error if the pattern is invalid

assert.raises 'regular expression', -> r'?\\'

returns a regex for a valid pattern

assert.is_not_nil r'foo()\\d+'

accepts a regex as well

assert.is_not_nil r r'foo()\\d+'

accepts and optional table of compile flags

reg = r '.', {r.DOTALL}
assert.is_truthy reg\match "\n"

accepts and optional table of match flags

reg = r 'x', nil, {r.MATCH_ANCHORED}
assert.is_falsy reg\match "ax"

match(string [, init])

returns nil if the pattern does not match

assert.is_nil r'foo'\match 'bar'

(with no captures in the pattern)

returns the entire match

assert.equal 'right', r'ri\\S+'\match 'red right hand'

(with captures in the pattern)

returns the captured values

assert.same { 'red', 'right' }, { r'(r\\w+)\\s+(\\S+)'\match 'red right hand' }

empty captures are returned as position captures

assert.same { 1, 4 }, { r'()red()'\match 'red' }

position captures are character based

assert.same { 2, 3 }, { r'å()ä()'\match 'åäö' }

(when init is specified)

matching starts from the init position

assert.equal 'right', r'r\\S+'\match 'red right hand', 2
assert.equal 6, r'r()\\S+'\match 'red right hand', 2

negative values counts from the end

assert.equal 'og', r'o\\w'\match 'top dog', -2

find(s, init)

returns nil if the pattern could not be found in <s>

assert.is_nil r'foo'\find 'bar'

returns the indices where the pattern match starts and end if found

assert.same { 2, 2 }, { r'\\pL'\find '!äö' }

returns any captures after the indices

assert.same { 2, 2, 'ä' }, { r'(\\pL)'\find '!äö' }

empty captures are returned as position captures

assert.same { 2, 2, 3 }, { r'\\pL()'\find '!äö' }

starts matching after init

assert.same { 3, 5, 6 }, { r'\\w+()'\find '12ab2', 3}

gmatch(s)

returns no matches when it does not match

matches = [m for m in r'\\d+'\gmatch 'well hello there']
assert.same {}, matches

(with no captures in the pattern)

produces each consecutive match in each call

matches = [m for m in r'\\w+'\gmatch 'well hello there']
assert.same { 'well', 'hello', 'there' }, matches

(with captures in the pattern)

returns empty captures as position matches

matches = [p for p in r'()\\pL+'\gmatch 'well hellö there' ]
assert.same { 1, 6, 12 }, matches

produces the the set of captures in each call

matches = [{p,m} for p,m in r'()(\\w+)'\gmatch 'well hello there']
assert.same { {1, 'well'}, {6, 'hello'}, {12, 'there'} }, matches
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/scintilla_spec.html b/site/source/versions/0.3/doc/spec/scintilla_spec.html deleted file mode 100644 index b18a06c95..000000000 --- a/site/source/versions/0.3/doc/spec/scintilla_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Scintilla - - - - - - - -
-
- -
- -

howl.Scintilla

local sci

before_each ->
  sci = Scintilla!

character_count()

returns the number of characters in the document

sci\insert_text 0, '123'
assert.equal 3, sci\character_count!
sci\insert_text 0, 'HƏllo'
assert.equal 8, sci\character_count!

is_multibyte()

returns true when the document contains multibyte characters

assert.is_false sci\is_multibyte()
sci\insert_text 0, 'HƏllo'
assert.is_true sci\is_multibyte()
sci\delete_range 1, 4
assert.is_false sci\is_multibyte()

char_offset(byte_offset)

returns the char_offset for the given byte_offset

sci\insert_text 0, 'äåö'
for p in *{
  {0, 0},
  {2, 1},
  {4, 2},
  {6, 3},
}
  assert.equal p[2], sci\char_offset p[1]

byte_offset(char_offset)

returns byte offsets for all character offsets passed as parameters

sci\insert_text 0, 'äåö'
for p in *{
  {0, 0},
  {2, 1},
  {4, 2},
  {6, 3},
}
  assert.equal p[1], sci\byte_offset p[2]

(offset handling stress test)

returns the correct result as compared to ustring

build = {}
line = 'äåöLinƏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣbutnowforsomesingleΤΥΦΧĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏ'
for i = 1,3000
  build[#build + 1] = line
s = table.concat build, '\n'
sci\insert_text 0, s

for i = 1, 3000 * line.ulen, 3007
  assert.equal s\byte_offset(i), sci\byte_offset(i - 1) + 1

for i = 1, 3000 * #line, 3007
  assert.equal s\char_offset(i), sci\char_offset(i - 1) + 1

for i = 2000 * #line, 100, -2003
  sci\delete_range i, 20
  sci\insert_text i, 'ordinary ascii text here'
  s = sci\get_text!
  c_offset = sci\char_offset(i + 100 - 1) + 1
  assert.equal s\char_offset(i + 100), c_offset
  assert.equal s\byte_offset(c_offset), sci\byte_offset(c_offset - 1) + 1

color handling

automatically converts between color values and strings

sci\style_set_fore 1, '#112233'
assert.equal sci\style_get_fore(1), '#112233'

colors can be specified by name

sci\style_set_fore 1, 'green'
assert.equal sci\style_get_fore(1), 'green'

.dispatch

calls handlers within a coroutine

in_coroutine = false
sci.listener = on_keypress: ->
  _, main = coroutine.running!
  in_coroutine = main == false
sci.dispatch sci.sci_ptr, 'key-press', {}
assert.is_true in_coroutine

returns the value returned by the handler

sci.listener = on_keypress: -> true
assert.is_true sci.dispatch sci.sci_ptr, 'key-press', {}

returns true to indicated handled if handler yields

sci.listener = on_keypress: -> coroutine.yield false
assert.is_true sci.dispatch sci.sci_ptr, 'key-press', {}

calls the on_error handler with the error if a handler raise one

sci.listener = {
  on_keypress: -> error 'BOOM!'
  on_error: spy.new -> nil
}
sci.dispatch sci.sci_ptr, 'key-press', {}
assert.spy(sci.listener.on_error).was_called_with('BOOM!')

(for key-press event)

calls the on_keypress handler if present

sci.listener = on_keypress: spy.new -> nil
sci.dispatch sci.sci_ptr, 'key-press', {}
assert.spy(sci.listener.on_keypress).was_called(1)
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/settings_spec.html b/site/source/versions/0.3/doc/spec/settings_spec.html deleted file mode 100644 index fc2c11b70..000000000 --- a/site/source/versions/0.3/doc/spec/settings_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.Settings - - - - - - - -
-
- -
- -

howl.Settings

local tmpdir, settings

before_each ->
  tmpdir = File.tmpdir!
  settings = Settings tmpdir

after_each ->
  tmpdir\rm_r!

.dir is set to the settings directory if available

assert.equal tmpdir, Settings(tmpdir).dir
assert.is_nil Settings(tmpdir\join('sub', 'bar')).dir

new(dir)

creates <dir> if does not exist, and its parent exists

target = tmpdir / 'foo'
Settings target
assert.is_true target.exists

target = tmpdir\join('bar', 'sub')
Settings target
assert.is_false target.exists

(when <dir> is not provided)

uses "$HOWL_DIR" when specified

with_tmpdir (dir) ->
  getenv = os.getenv
  os.getenv = (name) -> tostring dir.path if name == 'HOWL_DIR'
  pcall Settings
  os.getenv = getenv
  assert.is_true dir\join('system').exists

defaults to "$HOME/.howl"

with_tmpdir (dir) ->
  getenv = os.getenv
  os.getenv = (name) -> tostring dir.path if name == 'HOME'
  pcall Settings
  os.getenv = getenv
  assert.is_true dir\join('.howl').exists

load_user()

evaluates one of {init.lua, init.moon} if found, in that order

init_moon = tmpdir\join('init.moon')
init_moon.contents = '_G.__init_moon = true'

settings\load_user!
assert.equal true, __init_moon

_G.__init_moon = false
init_lua = tmpdir\join('init.lua')
init_lua.contents = '_G.__init_lua = true'

settings\load_user!
assert.is_true __init_lua
assert.is_false __init_moon

does nothing if the directory is not set

assert.has.no_error -> Settings(tmpdir\join('sub', 'bar'))\load_user!

raises an error if there is a problem loading the init file

tmpdir\join('init.moon').contents = 'UGH!'
assert.raises 'UGH', -> Settings(tmpdir)\load_user!

exposes a user_load helper for loading additional files

init_lua = tmpdir\join('init.lua')
tmpdir\join('more.lua').contents = 'return "more"'
sub = tmpdir\join('ext', 'sub.lua')
sub.parent\mkdir!
sub.contents = 'return "sub"'
init_lua.contents = [[
  _G.__loaded = user_load("more")
  _G.__loaded_sub = user_load("ext/sub")
]]

settings\load_user!
assert.equal "more", _G.__loaded
assert.equal "sub", _G.__loaded_sub

load_system(name)

returns the loaded contents of a file named system/<name>.lua

sysdir = tmpdir / 'system'
 sysdir\join('foo.lua').contents = 'return { a = "bar" }'
 assert.same {a: 'bar'}, settings\load_system 'foo'

returns nil if the the file does not exist

assert.is_nil settings\load_system 'no_such_file'

save_system(name, table)

saves the table to a loadeable file system/<name>.lua

settings\save_system 'saved', a: 'bar'
file = tmpdir / 'system/saved.lua'
assert.is_true file.exists
assert.same {a: 'bar'}, loadfile(file)!
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/signal_spec.html b/site/source/versions/0.3/doc/spec/signal_spec.html deleted file mode 100644 index f03a81f14..000000000 --- a/site/source/versions/0.3/doc/spec/signal_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.signal - - - - - - - -
-
- -
- -

howl.signal

.all contains all registered signals

signal.register 'foo', description: 'bar'
assert.same description: 'bar', signal.all.foo

.register(name, options)

raises an error if mandatory fields are missing

assert.raises 'description', -> signal.register 'foo'

.unregister(name)

unregisters the specified signal

signal.register 'frob', description: 'bar'
signal.unregister 'frob'
assert.is_nil signal.all.frob

(trying to use a non-registered signal)

emit raises an error

assert.raises 'none', -> signal.emit 'none'

connect raises an error

assert.raises 'none', -> signal.connect 'none', -> true

(with a registered signal)

before_each -> signal.register 'foo', description: 'bar'
after_each -> signal.unregister 'foo'

allows name based signals to be broadcasted to any number of handlers

handler1 = spy.new -> nil
handler2 = spy.new -> nil
signal.connect 'foo', handler1
signal.connect 'foo', handler2
signal.emit 'foo'
assert.spy(handler1).was_called!
assert.spy(handler2).was_called!

allows connecting handlers before existing handlers

value = nil
signal.connect 'foo', -> value = 'first'
signal.connect 'foo', (-> value = 'second'), 1
signal.emit 'foo'
assert.equal value, 'first'

allows disconnecting handlers

handler = spy.new -> true
signal.connect 'foo', handler
signal.disconnect 'foo', handler
signal.emit 'foo'
assert.spy(handler).was.not_called!

.emit

raises an error when called with more than two parameters

assert.raises 'parameter', -> signal.emit 'foo', {}, 2

raises an error when the second parameter is not a table

assert.raises 'table', -> signal.emit 'foo', 2

returns false if no handlers returned true

assert.is_false signal.emit 'foo'
signal.connect 'foo', -> 'this is fortunately not true'
assert.is_false signal.emit 'foo'

invokes all handlers in their own coroutines

coros = {}
coro_register = ->
  co, main = coroutine.running!
  coros[co] = true unless main

handler1 = spy.new coro_register
handler2 = spy.new coro_register
signal.connect 'foo', handler1
signal.connect 'foo', handler2
signal.emit 'foo'
assert.equal 2, #[v for _, v in pairs coros]

(when a handler returns true)

skips invoking subsequent handlers

handler2 = spy.new -> true
signal.connect 'foo', -> true
signal.connect 'foo', handler2
signal.emit 'foo'
assert.spy(handler2).was.not_called!

returns true

signal.connect 'foo', -> true
assert.is_true signal.emit 'foo'

(when a handler raises an error)

logs an error message

signal.connect 'foo', -> error 'BOOM'
signal.emit 'foo'
assert.match log.last_error.message, 'BOOM'

continues processing subsequent handlers

handler2 = spy.new -> true
signal.connect 'foo', -> error 'BOOM'
signal.connect 'foo', handler2
signal.emit 'foo'
assert.spy(handler2).was_called!

(when a handler yields)

continues on invoking subsequent handlers

handler2 = spy.new -> true
signal.connect 'foo', -> coroutine.yield false
signal.connect 'foo', handler2
signal.emit 'foo'
assert.spy(handler2).was_called!

returns false

signal.connect 'foo', -> coroutine.yield true
assert.is_false signal.emit 'foo'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/styler_spec.html b/site/source/versions/0.3/doc/spec/styler_spec.html deleted file mode 100644 index c0ae991c8..000000000 --- a/site/source/versions/0.3/doc/spec/styler_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.styler - - - - - - - -
-
- -
- -

howl.styler

sci = Scintilla!
buffer = Buffer {}, sci
sci.listener = buffer.sci_listener
style.define 's1', color: '#334455'
style.define 's2', color: '#334466'
style.define 's3', color: '#114466'

apply(buffer, start_pos, end_pos, styles)

styles the buffer text according to the styles

buffer.text = 'foo'
styler.apply buffer, 1, buffer.size, { 1, 's1', 2, 2, 's2', 4 }
assert.equal 's1', (style.at_pos(buffer, 1))
assert.equal 's2', (style.at_pos(buffer, 2))
assert.equal 's2', (style.at_pos(buffer, 3))

styles any holes with the default style

buffer.text = 'foo'
styler.apply buffer, 1, buffer.size, { 2, 's2', 3 }
assert.equal 'default', (style.at_pos(buffer, 1))
assert.equal 's2', (style.at_pos(buffer, 2))
assert.equal 'default', (style.at_pos(buffer, 3))

uses "default" for undefined styles

buffer.text = 'foo'
styler.apply buffer, 1, buffer.size, { 1, 'wat', 4 }
assert.equal 'default', (style.at_pos(buffer, 1))

(sub lexing)

automatically styles using extended styles when requested

buffer.text = '>foo'
styler.apply buffer, 1, buffer.size, {
  1, 'operator', 2,
  2, { 1, 's2', 2, 2, 's3', 3 }, 'my_sub|s1',
  4, 's2', 5
}
assert.equal 'operator', (style.at_pos(buffer, 1))
assert.equal 's1:s2', (style.at_pos(buffer, 2))
assert.equal 's1:s3', (style.at_pos(buffer, 3))
assert.equal 's2', (style.at_pos(buffer, 4))

styles any holes with the base style

buffer.text = 'foo'
styler.apply buffer, 1, buffer.size, {
  1, { 2, 's3', 3 }, 'my_sub|s1'
}
assert.equal 's1', (style.at_pos(buffer, 1))
assert.equal 's1:s3', (style.at_pos(buffer, 2))
assert.equal 'default', (style.at_pos(buffer, 3))

resets the base style afterwards

buffer.text = 'foo'
styler.apply buffer, 1, buffer.size, {
  1, { 1, 's3', 2 }, 'my_sub|s1'
}
assert.equal 's1:s3', (style.at_pos(buffer, 1))
assert.equal 'default', (style.at_pos(buffer, 2))

reverse(buffer, start_pos, end_pos)

returns a table of styles and positions for the given range, same as styles argument to apply

buffer.text = 'foo'
styles = { 1, 's1', 2, 2, 's2', 4 }
styler.apply buffer, 1, buffer.size, styles
assert.same styles, styler.reverse buffer, 1, #buffer

handles "gaps" for characters with the default style

buffer.text = 'foobar'
styles = { 1, 's1', 2, 4, 's2', 7 }
styler.apply buffer, 1, buffer.size, styles
assert.same styles, styler.reverse buffer, 1, #buffer

end_pos is inclusive

buffer.text = 'foo'
styles = { 1, 's1', 2, 2, 's2', 4 }
styler.apply buffer, 1, buffer.size, styles
assert.same { 1, 's1', 2 }, styler.reverse buffer, 1, 1

indexes are byte offsets

buffer.text = 'Liñe'
styles = { 1, 's1', 2 }
styler.apply buffer, buffer.size, buffer.size, styles
assert.same { 1, 'unstyled', 2 }, styler.reverse buffer, 4, 4
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/sys_spec.html b/site/source/versions/0.3/doc/spec/sys_spec.html deleted file mode 100644 index 267683cf0..000000000 --- a/site/source/versions/0.3/doc/spec/sys_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.env - - - - - - - -
-
- -
- -

howl.env

env = sys.env

allows reading environment variables using plain indexing

assert.equals 'string', type env.HOME
assert.equals 'string', type env['HOME']
assert.equals os.getenv('HOME'), env.HOME

allows setting variables via assignment

env.MY_VAR = 'myval'
assert.equals 'myval', env.MY_VAR
assert.equals 'myval', os.getenv('MY_VAR')

allows unsetting variables using a nil assignment

env.MY_VAR = 'myval'
assert.equals 'myval', os.getenv('MY_VAR')
env.MY_VAR = nil
assert.is_nil env.MY_VAR
assert.is_nil os.getenv('MY_VAR')

allows iterating over the env using pairs

env.MY_VAR = 'yowser!'
as_table = {k,v for k,v in pairs env}
assert.equals 'yowser!', as_table.MY_VAR
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/timer_spec.html b/site/source/versions/0.3/doc/spec/timer_spec.html deleted file mode 100644 index f5dffa6c8..000000000 --- a/site/source/versions/0.3/doc/spec/timer_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.timer - - - - - - - -
-
- -
- -

howl.timer

setup -> set_howl_loop!

asap(f, ...)

it 'invokes <f> once as part of the next main loop iteration', (done) ->

timer.asap async ->
  done!

it 'passes along any additional arguments as is', (done) ->

callback = async (...) ->
  assert.same { 'one', nil, 3 }, { ... }
  done!

timer.asap callback, 'one', nil, 3

after(seconds, f, ...)

it 'invokes <f> once after approximately <seconds>', (done) ->

timer.after 0, async ->
  done!
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/action_buffer_spec.html b/site/source/versions/0.3/doc/spec/ui/action_buffer_spec.html deleted file mode 100644 index 97573f702..000000000 --- a/site/source/versions/0.3/doc/spec/ui/action_buffer_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.ActionBuffer - - - - - - - -
-
- -
- -

howl.ui.ActionBuffer

sci = Scintilla!
buf = ActionBuffer sci
sci.listener = buf.sci_listener

before_each -> buf.text = ''

initialization takes an optional sci parameter

assert.not_error -> ActionBuffer!
assert.equal ActionBuffer(sci).sci, sci

behaves like a Buffer

buf.text = 'hello'
assert.equal buf.text, 'hello'
buf\append ' world'
assert.equal buf.text, 'hello world'

.insert(object, pos[ , style])

(with no specified style)

inserts the object with no specific style and returns the next position

assert.equals 6, buf\insert 'hello', 1
assert.equal style.at_pos(buf, 1), 'unstyled'

(with style specified)

styles the object with the specified style

buf.text = '˫˫'
buf\insert 'hƏllo', 2, 'keyword'
assert.equal 'unstyled', (style.at_pos(buf, 1))
assert.equal 'keyword', (style.at_pos(buf, 2))
assert.equal 'keyword', (style.at_pos(buf, 6))
assert.equal 'unstyled', (style.at_pos(buf, 7))

styles the text with the default style if the style is unknown

buf\insert 'hello', 1, 'what?'
assert.equal style.at_pos(buf, 1), 'default'

(when object is a styled object (.styles is present))

inserts the corresponding .text and returns the next position

buf\insert 'foo', 1
chunk = buf\chunk(1, 3)
assert.equal 7, buf\insert chunk, 4
assert.equal 'foofoo', buf.text

styles the inserted .text using .styles for the styling

buf\insert {text: 'styled', styles: { 2, 'keyword', 3, 3, 'number', 6}}, 1
assert.equal 'default', (style.at_pos(buf, 1))
assert.equal 'keyword', (style.at_pos(buf, 2))
assert.equal 'number', (style.at_pos(buf, 3))
assert.equal 'number', (style.at_pos(buf, 5))
assert.equal 'default', (style.at_pos(buf, 6))
assert.equal 'styled', buf.text

still returns the next position

assert.equal 3, buf\insert StyledText('åö', {}), 1

ignores any given <style> parameter

buf\insert StyledText('foo', { 1, 'number', 4 }), 1, 'keyword'
assert.equal 'number', (style.at_pos(buf, 1))

.append(text, style)

(with no specified style)

appends the text with no specific style and returns the next position

buf.text = 'hello'
assert.equal #'hello world' + 1, buf\append ' world'
assert.equal style.at_pos(buf, 7), 'unstyled'

(with style specified)

styles the text with the specified style

buf.text = '˫'
buf\append 'hƏllo', 'keyword'
assert.equal style.at_pos(buf, 1), 'unstyled'
assert.equal style.at_pos(buf, 2), 'keyword'
assert.equal style.at_pos(buf, 6), 'keyword'

styles the text with the default style if the style is unknown

buf\append 'again', 'what?'
assert.equal 'default', (style.at_pos(buf, buf.length - 1))

(when object is a styled object)

appends the corresponding text and returns the next position

buf\insert 'foo', 1
chunk = buf\chunk(1, 3)
assert.equals 7, buf\append chunk
assert.equal 'foofoo', buf.text

styles the inserted text using .styles for the styling

buf.text = 'foo'
object = StyledText('bar', {1, 'number', 2, 2, 'keyword', 3})
buf\insert object, 4
assert.equal 'foobar', buf.text
assert.equal 'number', (style.at_pos(buf, 4))
assert.equal 'keyword', (style.at_pos(buf, 5))
assert.equal 'default', (style.at_pos(buf, 6))

still returns the next position

assert.equal 3, buf\append StyledText('åö', {})

ignores any given <style> parameter

buf\append StyledText('foo', { 1, 'number', 4 }), 'keyword'
assert.equal 'number', (style.at_pos(buf, 1))

style(start_pos, end_pos, style)

applies <style> for the inclusive text range given

buf.text = 'hƏlɩo'
buf\style 2, 4, 'keyword'
assert.equal style.at_pos(buf, 1), 'unstyled'
assert.equal style.at_pos(buf, 2), 'keyword'
assert.equal style.at_pos(buf, 4), 'keyword'
assert.equal style.at_pos(buf, 5), 'unstyled'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/command_line_spec.html b/site/source/versions/0.3/doc/spec/ui/command_line_spec.html deleted file mode 100644 index a51fbe2b1..000000000 --- a/site/source/versions/0.3/doc/spec/ui/command_line_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.CommandLine - - - - - - - -
-
- -
- -

howl.ui.CommandLine

local command_line, run_as_handler

run_in_coroutine = (f) ->
  wrapped = coroutine.wrap -> f!
  return wrapped!

before_each ->
  app.window = Window!
  command_line = app.window.command_line
  run_as_handler = (f) ->
    command_line\run
      name: 'within-activity'
      handler: -> f!

after_each ->
  ok, result = pcall -> app.window.command_line\abort_all!
  if not ok
    print result
  run_as_handler = nil
  command_line = nil
  app.window = nil

command_line

\\run(activity_spec)

errors if handler or factory field not in spec

f = -> command_line\run {}
assert.raises 'requires "name" and one of "handler" or "factory" fields', f

for activity with handler

calls activity handler

handler = spy.new ->
command_line\run
  name: 'run-activity'
  handler: handler
assert.spy(handler).was_called 1

passes any extra args to handler

handler = spy.new ->
aspec =
  name: 'run-activity'
  handler: handler
command_line\run aspec, 'a1', 'a2', 'a3'
assert.spy(handler).was_called_with 'a1', 'a2', 'a3'

returns result of handler

result = command_line\run
  name: 'run-activity'
  handler: -> return 'r1'
assert.equal 'r1', result

for activity with factory

instantiates facory, calls run method

handler = spy.new ->
run_in_coroutine ->
  command_line\run
    name: 'run-factory'
    factory: ->
      run: handler
assert.spy(handler).was_called 1

passes instantiated object, finish function, extra args to run

local args
obj = run: (...) ->
  args = {...}

aspec =
  name: 'run-factory'
  factory: -> obj
run_in_coroutine ->
  command_line\run aspec, 'a1', 'a2'

assert.equal args[1], obj
assert.equal 'function', type(args[2])
assert.equal 'a1', args[3]
assert.equal 'a2', args[4]

returns result passed in finish function

local result
run_in_coroutine ->
  result = command_line\run
    name: 'run-factory'
    factory: ->
      run: (finish) => finish('r2')
assert.equal 'r2', result

\\run_after_finish(f)

calls f! immediately after the current activity stack exits

nextf = spy.new ->
command_line\run
  name: 'run-activity'
  handler: ->
    command_line\run_after_finish nextf
assert.spy(nextf).was_called 1

.text

cannot be set when no running activity

f = -> command_line.text = 'hello'
assert.raises 'no running activity', f

returns nil when no running activity

assert.equals nil, command_line.text

updates the text displayed in the command_widget

run_as_handler ->
  command_line.text = 'hello'
  assert.equal 'hello', command_line.command_widget.text
  command_line.text = 'bye'
  assert.equal 'bye', command_line.command_widget.text

returns the text previously set

run_as_handler ->
  assert.equal command_line.text, ''
  command_line.text = 'hi'
  assert.equal 'hi', command_line.text

.prompt

does not work when no running activity

f = -> command_line.prompt = 'hello'
assert.raises 'no running activity', f

updates the prompt displayed in the command_widget

run_as_handler ->
  command_line.prompt = 'hello'
  assert.equal 'hello', command_line.command_widget.text
  command_line.prompt = 'bye'
  assert.equal 'bye', command_line.command_widget.text

returns the prompt previously set

run_as_handler ->
  command_line.prompt = 'set'
  assert.equal 'set', command_line.prompt

title

is hidden by default

run_as_handler ->
  assert.is_false command_line.header\to_gobject!.visible

is shown and updated by setting .title

run_as_handler ->
  command_line.title = 'Nice Title'
  assert.equal 'Nice Title', command_line.indic_title.label
  assert.is_true command_line.header\to_gobject!.visible

is hidden by setting title to empty string

run_as_handler ->
  command_line.title = 'Nice Title'
  assert.is_true command_line.header\to_gobject!.visible
  command_line.title = ''
  assert.is_false command_line.header\to_gobject!.visible

is restored to the one set by the current interaction

run_as_handler ->
  command_line.title = 'Title 0'
  assert.equal 'Title 0', command_line.indic_title.label

  run_as_handler ->
    command_line.title = 'Title 1'
    assert.equal 'Title 1', command_line.indic_title.label

  assert.equal 'Title 0', command_line.indic_title.label

when using both .prompt and .text

the prompt is displayed before the text

run_as_handler ->
  command_line.prompt = 'prómpt:'
  command_line.text = 'téxt'
  assert.equal 'prómpt:téxt', command_line.command_widget.text

preserves text when updating prompt

run_as_handler ->
  command_line.prompt = 'héllo:'
  command_line.text = 'téxt'
  assert.equal 'héllo:téxt', command_line.command_widget.text
  command_line.prompt = 'hóla:'
  assert.equal 'hóla:téxt', command_line.command_widget.text

preserves prompt when updating téxt

run_as_handler ->
  command_line.prompt = 'héllo:'
  command_line.text = 'téxt '
  assert.equal 'héllo:téxt ', command_line.command_widget.text
  command_line.text = 'hóla'
  assert.equal 'héllo:hóla', command_line.command_widget.text

(clear())

clears the text only, leaving prompt intact

run_as_handler ->
  command_line.prompt = 'héllo:'
  command_line.text = 'téxt'
  command_line\clear!
  assert.equal 'héllo:', command_line.command_widget.text

when using nested interactions

each interaction has independent prompt and text

run_as_handler ->
  command_line.prompt = 'outer:'
  command_line.text = '0'
  assert.equal 'outer:0', command_line.command_widget.text

  run_as_handler ->
    command_line.prompt = 'inner:'
    command_line.text = '1'
    assert.equal 'outer:0inner:1', command_line.command_widget.text
    command_line.prompt = 'later:'
    assert.equal 'outer:0later:1', command_line.command_widget.text

  assert.equal 'outer:0', command_line.command_widget.text

.stack_depth returns number of running activities

depths = {}
table.insert depths, command_line.stack_depth
run_as_handler ->
  table.insert depths, command_line.stack_depth
  run_as_handler ->
    table.insert depths, command_line.stack_depth
    run_as_handler ->
      table.insert depths, command_line.stack_depth
    table.insert depths, command_line.stack_depth
  table.insert depths, command_line.stack_depth
table.insert depths, command_line.stack_depth

assert.same { 0, 1, 2, 3, 2, 1, 0 }, depths

\\abort_all! cancels all running activities

depths = {}
table.insert depths, command_line.stack_depth
run_as_handler ->
  table.insert depths, command_line.stack_depth
  run_as_handler ->
    table.insert depths, command_line.stack_depth
    run_as_handler ->
      table.insert depths, command_line.stack_depth
      command_line\abort_all!
      table.insert depths, command_line.stack_depth

assert.same { 0, 1, 2, 3, 0 }, depths

finishing any activity aborts all nested activities

depths = {}
table.insert depths, command_line.stack_depth
run_as_handler ->
  dispatch.launch ->
    table.insert depths, command_line.stack_depth
    p = dispatch.park 'command_line_test'
    dispatch.launch ->
      run_as_handler -> run_as_handler ->
          table.insert depths, command_line.stack_depth
          dispatch.resume p

    dispatch.wait p
    table.insert depths, command_line.stack_depth

assert.same {0, 1, 3, 1}, depths

\\clear_all! clears the entire command line and restores on exit

texts = {}
run_as_handler ->
  command_line.prompt = 'outér:'
  command_line.text = '0'
  run_as_handler ->
    table.insert texts, command_line.command_widget.text
    command_line\clear_all!
    table.insert texts, command_line.command_widget.text
    command_line.prompt = 'innér:'
    command_line.text = '1'
    table.insert texts, command_line.command_widget.text
  table.insert texts, command_line.command_widget.text
assert.same { 'outér:0', '', 'innér:1', 'outér:0' }, texts
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/cursor_spec.html b/site/source/versions/0.3/doc/spec/ui/cursor_spec.html deleted file mode 100644 index 2f327c9ff..000000000 --- a/site/source/versions/0.3/doc/spec/ui/cursor_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.Cursor - - - - - - - -
-
- -
- -

howl.ui.Cursor

buffer = Buffer {}
editor = Editor buffer
cursor = editor.cursor
selection = editor.selection

before_each ->
  buffer.text = text
  cursor.pos = 1
  selection.persistent = false

.at_end_of_line returns true if cursor is at the end of the line

cursor.pos = 1
assert.is_false cursor.at_end_of_line
cursor.column = 15
assert.is_true cursor.at_end_of_line

.at_start_of_line returns true if cursor is at the start of the line

cursor.pos = 1
assert.is_true cursor.at_start_of_line
cursor.line = 2
assert.is_true cursor.at_start_of_line

.at_end_of_file returns true if cursor is at the end of the buffer

cursor.pos = 1
assert.is_false cursor.at_end_of_file
cursor\eof!
assert.is_true cursor.at_end_of_file

move_to(line, column) moves the cursor to the specified line and column

buffer.text = 'hello\nworld'
cursor\move_to 1, 3
assert.equal 3, cursor.pos
cursor\move_to 2, 2
assert.equal 8, cursor.pos

down! moves the cursor one line down, respecting the current column

cursor.pos = 4
cursor\down!
assert.equal 2, cursor.line
assert.equal 4, cursor.column

up! moves the cursor one line up, respecting the current column

cursor.line = 2
cursor.column = 3
cursor\up!
assert.equal 1, cursor.line
assert.equal 3, cursor.column

right! moves the cursor one char right

cursor.pos = 1
cursor\right!
assert.equal cursor.pos, 2

left! moves the cursor one char left

cursor.pos = 3
cursor\left!
assert.equal cursor.pos, 2

.style

is "line" by default

assert.equal 'line', cursor.style

raises an error if set to anything else than "block" or "line"

cursor.style = 'block'
cursor.style = 'line'
assert.raises 'foo', -> cursor.style = 'foo'

.pos

reading returns the current character position in one based index

editor.sci\goto_pos 4 -- raw zero-based sci access, really at 'e'
assert.equal 4, cursor.pos

setting sets the current position

cursor.pos = 4
assert.equal cursor.pos, 4

setting adjusts the selection if it is persistent

selection\set 1, 2
selection.persistent = true
cursor.pos = 5
assert.equal cursor.pos, 5
assert.equals 'Liñe', selection.text

out-of-bounds values are automatically corrected

cursor.pos = 0
assert.equal 1, cursor.pos
cursor.pos = -1
assert.equal 1, cursor.pos
cursor.pos = math.huge
assert.equal #buffer + 1, cursor.pos
cursor.pos = #buffer + 2
assert.equal #buffer + 1, cursor.pos

.line

returns the current line

cursor.pos = 1
assert.equal cursor.line, 1

setting moves the cursor to the first column of the specified line

cursor.line = 2
assert.equal cursor.pos, 16

assignment adjusts out-of-bounds values automatically

cursor.line = -1
assert.equal 1, cursor.pos
cursor.line = 100
assert.equal #buffer + 1, cursor.pos

assignment adjusts the selection if it is persistent

cursor.pos = 1
selection.persistent = true
cursor.line = 2
assert.equals 'Liñe 1 ʘf tƏxt\n', selection.text

.column

returns the current column

cursor.pos = 4
assert.equal cursor.column, 4

.column = <nr>

moves the cursor to the specified column

cursor.column = 2
assert.equal 2, cursor.pos

takes tabs into account

buffer.config.tab_width = 4
buffer.text = '\tsome text after'
cursor.pos = 1
cursor.column = 5
assert.equal 2, cursor.pos

adjusts the selection if it is persistent

cursor.pos = 1
selection.persistent = true
cursor.column = 5
assert.equals 'Liñe', selection.text

.column_index

returns the real column index for the current line disregarding tabs

buffer.config.tab_width = 4
  buffer.text = '\tsome text'
  cursor.pos = 2
  assert.equal 5, cursor.column
  assert.equal 2, cursor.column_index

returns the column index as a character offset

buffer.text = 'åäö\nåäö'
  cursor.pos = 6
  assert.equal 2, cursor.column_index

adjusts the selection if it is persistent

buffer.text = 'åäö'
cursor.pos = 1
selection.persistent = true
cursor.column_index = 3
assert.equals 'åä', selection.text

.column_index = <nr>

before_each ->
  buffer.config.tab_width = 4
  buffer.text = '\tsome text after'

moves the cursor to the specified column index

cursor.column_index = 2
assert.equal 2, cursor.column_index
assert.equal 5, cursor.column

treats <nr> as a character offset

buffer.text = 'åäö\nåäö'
cursor.line = 2
cursor.column_index = 2
assert.equal 2, cursor.column_index
assert.equal 6, cursor.pos

(when passing true for extended_selection to movement commands)

the selection is extended along with moving the cursor

sel = editor.selection
cursor.pos = 1
cursor\right true
assert.equal 'L', sel.text
cursor\down true
assert.equals 'Liñe 1 ʘf tƏxt\nA', sel.text
cursor\left true
assert.equals 'Liñe 1 ʘf tƏxt\n', sel.text
cursor\up true
assert.is_true sel.empty

(when the editor selection is marked as persistent)

the selection is extended along with moving the cursor

sel = editor.selection
sel.persistent = true
cursor.pos = 1
cursor\right!
assert.equal 'L', sel.text
cursor.line = 2
assert.equals 'Liñe 1 ʘf tƏxt\n', sel.text
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/editor_spec.html b/site/source/versions/0.3/doc/spec/ui/editor_spec.html deleted file mode 100644 index dee48e184..000000000 --- a/site/source/versions/0.3/doc/spec/ui/editor_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.Editor - - - - - - - -
-
- -
- -

howl.ui.Editor

local buffer, lines
editor = Editor Buffer {}
cursor = editor.cursor
selection = editor.selection
window = Gtk.OffscreenWindow!
window\add editor\to_gobject!
window\show_all!

before_each ->
  buffer = Buffer {}
  buffer.config.indent = 2
  lines = buffer.lines
  editor.buffer = buffer
  selection\remove!

.current_line is a shortcut for the current buffer line

buffer.text = 'hƏllo\nworld'
cursor.pos = 2
assert.equal editor.current_line, buffer.lines[1]

.current_context returns the buffer context at the current position

buffer.text = 'hƏllo\nwʘrld'
cursor.pos = 2
context = editor.current_context
assert.equal 'Context', typeof context
assert.equal 2, context.pos

.newline() adds a newline at the current position

buffer.text = 'hƏllo'
cursor.pos = 3
editor\newline!
assert.equal buffer.text, 'hƏ\nllo'

insert(text) inserts the text at the cursor, and moves cursor after text

buffer.text = 'hƏllo'
cursor.pos = 6
editor\insert ' world'
assert.equal 'hƏllo world', buffer.text
assert.equal 12, cursor.pos, 12

delete_line() deletes the current line

buffer.text = 'hƏllo\nworld!'
cursor.pos = 3
editor\delete_line!
assert.equal 'world!', buffer.text

copy_line() copies the current line

buffer.text = 'hƏllo\n'
cursor.pos = 3
editor\copy_line!
cursor.pos = 1
editor\paste!
assert.equal 'hƏllo\nhƏllo\n', buffer.text

join_lines joins the current line with the one after

buffer.text = 'hƏllo\n    world!'
cursor.pos = 1
editor\join_lines!
assert.equal 'hƏllo world!', buffer.text
assert.equal 6, cursor.pos

forward_to_match(string) moves the cursor to next occurence of <string>, if found in the line

buffer.text = 'hƏll\to\n    world!'
cursor.pos = 1
editor\forward_to_match 'l'
assert.equal 3, cursor.pos
editor\forward_to_match 'l'
assert.equal 4, cursor.pos
editor\forward_to_match 'o'
assert.equal 6, cursor.pos
editor\forward_to_match 'w'
assert.equal 6, cursor.pos

backward_to_match(string) moves the cursor back to previous occurence of <string>, if found in the line

buffer.text = 'h\tƏllo\n    world!'
cursor.pos = 6
editor\backward_to_match 'l'
assert.equal 5, cursor.pos
editor\backward_to_match 'l'
assert.equal 4, cursor.pos
editor\backward_to_match 'h'
assert.equal 1, cursor.pos
editor\backward_to_match 'w'
assert.equal 1, cursor.pos

.active_lines

(with no selection active)

is a table containing .current_line

buffer.text = 'hƏllo\nworld'
lines = editor.active_lines
assert.equals 1, #lines
assert.equals editor.current_line, lines[1]

(with a selection active)

is a table of lines involved in the selection

buffer.text = 'hƏllo\nworld'
selection\set 3, 8
active_lines = editor.active_lines
assert.equals 2, #active_lines
assert.equals lines[1], active_lines[1]
assert.equals lines[2], active_lines[2]

.active_chunk

is a chunk

assert.equals 'Chunk', typeof editor.active_chunk

(with no selection active)

is a chunk encompassing the entire buffer text

buffer.text = 'hƏllo\nworld'
assert.equals 'hƏllo\nworld', editor.active_chunk.text

(with a selection active)

is a chunk containing the current the selection

buffer.text = 'hƏllo\nworld'
selection\set 3, 8
assert.equals 'llo\nw', editor.active_chunk.text

indent()

(when mode does not provide a indent method)

does nothing

text = buffer.text
editor[method] editor
assert.equal text, buffer.text

(when mode provides a indent method)

calls that passing itself a parameter

buffer.mode = [method]: spy.new -> nil
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with buffer.mode, editor

comment()

(when mode does not provide a comment method)

does nothing

text = buffer.text
editor[method] editor
assert.equal text, buffer.text

(when mode provides a comment method)

calls that passing itself a parameter

buffer.mode = [method]: spy.new -> nil
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with buffer.mode, editor

uncomment()

(when mode does not provide a uncomment method)

does nothing

text = buffer.text
editor[method] editor
assert.equal text, buffer.text

(when mode provides a uncomment method)

calls that passing itself a parameter

buffer.mode = [method]: spy.new -> nil
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with buffer.mode, editor

toggle_comment()

(when mode does not provide a toggle_comment method)

does nothing

text = buffer.text
editor[method] editor
assert.equal text, buffer.text

(when mode provides a toggle_comment method)

calls that passing itself a parameter

buffer.mode = [method]: spy.new -> nil
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with buffer.mode, editor

with_position_restored(f)

before_each ->
  buffer.text = '  yowser!\n  yikes!'
  cursor.line = 2
  cursor.column = 4

calls <f> passing itself a parameter

f = spy.new -> nil
editor\with_position_restored f
assert.spy(f).was_called_with editor

restores the cursor position afterwards

editor\with_position_restored -> cursor.pos = 2
assert.equals 2, cursor.line
assert.equals 4, cursor.column

adjusts the position should the indentation have changed

editor\with_position_restored ->
  lines[1].indentation = 0
  lines[2].indentation = 0

assert.equals 2, cursor.line
assert.equals 2, cursor.column

editor\with_position_restored ->
  lines[1].indentation = 3
  lines[2].indentation = 3

assert.equals 2, cursor.line
assert.equals 5, cursor.column

(when <f> raises an error)

propagates the error

assert.raises 'ARGH!', -> editor\with_position_restored -> error 'ARGH!'

the position is still restored

cursor.pos = 4

pcall editor.with_position_restored, editor, ->
  cursor.pos = 2
  error 'ARGH!'

assert.equals 4, cursor.pos

paste(opts = {})

pastes the current clip of the clipboard at the current position

buffer.text = 'hƏllo'
clipboard.push ' wörld'
cursor\eof!
editor\paste!
assert.equal 'hƏllo wörld', buffer.text

(when opts.clip is specified)

pastes that clip at the current position

clipboard.push 'hello'
clip = clipboard.current
clipboard.push 'wörld'
buffer.text = 'well '
cursor\eof!
editor\paste :clip
assert.equal 'well hello', buffer.text

(when opts.where is set to "after")

pastes the clip to the right of the current position

buffer.text = 'hƏllo\n'
clipboard.push 'yo'
cursor\move_to 1, 6
editor\paste where: 'after'
assert.equal 'hƏllo yo\n', buffer.text
cursor\eof!
editor\paste where: 'after'
assert.equal 'hƏllo yo\n yo', buffer.text

(when the clip item has .whole_lines set)

pastes the clip on a newly opened line above the current

buffer.text = 'hƏllo\nworld'
clipboard.push text: 'cruel', whole_lines: true
cursor.line = 2
cursor.column = 3
editor\paste!
assert.equal 'hƏllo\ncruel\nworld', buffer.text

pastes the clip at the start of a line if ends with a newline separator

buffer.text = 'hƏllo\nworld'
clipboard.push text: 'cruel\n', whole_lines: true
cursor.line = 2
cursor.column = 3
editor\paste!
assert.equal 'hƏllo\ncruel\nworld', buffer.text

positions the cursor at the start of the pasted clip

buffer.text = 'paste'
clipboard.push text: 'much', whole_lines: true
cursor.column = 3
editor\paste!
assert.equal 1, cursor.pos

(.. when opts.where is set to "after")

pastes the clip on a newly opened line below the current

buffer.text = 'hƏllo\nworld'
clipboard.push text: 'cruel', whole_lines: true
cursor.line = 1
cursor.column = 3
editor\paste where: 'after'
assert.equal 'hƏllo\ncruel\nworld', buffer.text

(when a selection is present)

deletes the selection before pasting

buffer.text = 'hƏllo\nwonderful\nworld'
clipboard.push text: 'cruel'
selection\select lines[2].start_pos, lines[2].end_pos - 1
editor\paste!
assert.equal 'hƏllo\ncruel\nworld', buffer.text
assert.equal 'cruel', clipboard.current.text

delete_to_end_of_line(no_copy)

cuts text from cursor up to end of line

buffer.text = 'hƏllo world!\nnext'
cursor.pos = 6
editor\delete_to_end_of_line!
assert.equal buffer.text, 'hƏllo\nnext'
editor\paste!
assert.equal buffer.text, 'hƏllo world!\nnext'

deletes without copying if no_copy is specified

buffer.text = 'hƏllo world!'
cursor.pos = 3
editor\delete_to_end_of_line true
assert.equal buffer.text, 'hƏ'
editor\paste!
assert.not_equal 'hƏllo world!', buffer.text

(buffer switching)

remembers the position for different buffers

buffer.text = 'hƏllo\n    world!'
cursor.pos = 8
buffer2 = Buffer {}
buffer2.text = 'a whole different whale'
editor.buffer = buffer2
cursor.pos = 15
editor.buffer = buffer
assert.equal 8, cursor.pos
editor.buffer = buffer2
assert.equal 15, cursor.pos

updates .last_shown for buffer switched out

time = os.time
now = time!
os.time = -> now
pcall ->
  editor.buffer = Buffer {}
os.time = time
assert.same now, buffer.last_shown

(previewing)

does not update last_shown for previewed buffer

new_buffer = Buffer {}
new_buffer.last_shown = 2
editor\preview new_buffer
editor.buffer = Buffer {}
assert.same 2, new_buffer.last_shown

updates .last_shown for original buffer switched out

time = os.time
now = time!
os.time = -> now
pcall ->
  editor\preview Buffer {}
os.time = time
assert.same now, buffer.last_shown

(indentation, tabs, spaces and backspace)

defines a "tab_width" config variable, defaulting to 8

assert.equal config.tab_width, 4

defines a "use_tabs" config variable, defaulting to false

assert.equal config.use_tabs, false

defines a "indent" config variable, defaulting to 2

assert.equal config.indent, 2

defines a "tab_indents" config variable, defaulting to true

assert.equal config.tab_indents, true

defines a "backspace_unindents" config variable, defaulting to true

assert.equal config.backspace_unindents, true

smart_tab()

inserts a tab character if use_tabs is true

config.use_tabs = true
buffer.text = 'hƏllo'
cursor.pos = 2
editor\smart_tab!
assert.equal buffer.text, 'h\tƏllo'

inserts spaces to move to the next tab if use_tabs is false

config.use_tabs = false
buffer.text = 'hƏllo'
cursor.pos = 1
editor\smart_tab!
assert.equal string.rep(' ', config.indent) .. 'hƏllo', buffer.text

inserts a tab to move to the next tab stop if use_tabs is true

config.use_tabs = true
config.tab_width = config.indent
buffer.text = 'hƏllo'
cursor.pos = 1
editor\smart_tab!
assert.equal '\thƏllo', buffer.text

indents the current line if in whitespace and tab_indents is true

config.use_tabs = false
config.tab_indents = true
indent = string.rep ' ', config.indent
buffer.text = indent .. 'hƏllo'
cursor.pos = 2
editor\smart_tab!
assert.equal buffer.text, string.rep(indent, 2) .. 'hƏllo'

.delete_back()

deletes back by one character

buffer.text = 'hƏllo'
cursor.pos = 2
editor\delete_back!
assert.equal buffer.text, 'Əllo'

unindents if in whitespace and backspace_unindents is true

config.indent = 2
buffer.text = '  hƏllo'
cursor.pos = 3
config.backspace_unindents = true
editor\delete_back!
assert.equal buffer.text, 'hƏllo'

deletes back if in whitespace and backspace_unindents is false

config.indent = 2
buffer.text = '  hƏllo'
cursor.pos = 3
config.backspace_unindents = false
editor\delete_back!
assert.equal buffer.text, ' hƏllo'

.delete_forward()

deletes the character at cursor

buffer.text = 'hƏllo'
cursor.pos = 2
editor\delete_forward!
assert.equal 'hllo', buffer.text

(when a selection is active)

deletes the selection

buffer.text = 'hƏllo'
editor.selection\set 2, 5
editor\delete_forward!
assert.equal 'ho', buffer.text
assert.not_equal 'Əll', clipboard.current.text

(when at the end of a line)

deletes the line break

buffer.text = 'hƏllo\nworld'
cursor\move_to 1, 6
editor\delete_forward!
assert.equal 'hƏlloworld', buffer.text

(when at the end of the buffer)

does nothing

buffer.text = 'hƏllo'
cursor\eof!
editor\delete_forward!
assert.equal 'hƏllo', buffer.text

.shift_right()

right-shifts the lines included in a selection if any

config.indent = 2
buffer.text = 'hƏllo\nselected\nworld!'
selection\set 2, 10
editor\shift_right!
assert.equal buffer.text, '  hƏllo\n  selected\nworld!'

right-shifts the current line when nothing is selected, remembering column

config.indent = 2
buffer.text = 'hƏllo\nworld!'
cursor.pos = 3
editor\shift_right!
assert.equal buffer.text, '  hƏllo\nworld!'
assert.equal cursor.pos, 5

.shift_left()

left-shifts the lines included in a selection if any

config.indent = 2
buffer.text = '  hƏllo\n  selected\nworld!'
selection\set 4, 12
editor\shift_left!
assert.equal buffer.text, 'hƏllo\nselected\nworld!'

left-shifts the current line when nothing is selected, remembering column

config.indent = 2
buffer.text = '    hƏllo\nworld!'
cursor.pos = 4
editor\shift_left!
assert.equal buffer.text, '  hƏllo\nworld!'
assert.equal cursor.pos, 2

(events)

on char added

emits a character-added event with the passed arguments merged with the editor reference

handler = spy.new -> true
signal.connect 'character-added', handler
args = key_name: 'a'
editor\_on_char_added args
signal.disconnect 'character-added', handler
args.editor = editor
assert.spy(handler).was_called_with args

invokes mode.on_char_added if present, passing (arguments, editor)

buffer.mode = on_char_added: spy.new -> nil
args = key_name: 'a', :editor
editor\_on_char_added args
assert.spy(buffer.mode.on_char_added).was_called_with buffer.mode, args, editor

(resource management)

editors are collected as they should

e = Editor Buffer {}
editors = setmetatable {}, __mode: 'v'
append editors, e
e = nil
collectgarbage!
assert.is_nil editors[1]

cycle_case()

(with a selection active)

changes all lowercase selection to all uppercase

buffer.text = 'hello selectëd #world'
selection\set 7, 22
editor\cycle_case!
assert.equals 'hello SELECTËD #WORLD', buffer.text

changes all uppercase selection to titlecase

buffer.text = 'hello SELECTËD #WORLD HELLO'
selection\set 7, 28
editor\cycle_case!
assert.equals 'hello Selectëd #world Hello', buffer.text

changes mixed case selection to all lowercase

buffer.text = 'hello SelectËD #WorLd'
selection\set 7, 22
editor\cycle_case!
assert.equals 'hello selectëd #world', buffer.text

preserves selection

buffer.text = 'select'
selection\set 3, 5
editor\cycle_case!
assert.equals 3, selection.anchor
assert.equals 5, selection.cursor

(with no selection active)

changes all lowercase word to all uppercase

buffer.text = 'hello wörld'
editor.cursor.pos = 7
editor\cycle_case!
assert.equals 'hello WÖRLD', buffer.text

changes all uppercase word to titlecase

buffer.text = 'hello WÖRLD'
editor.cursor.pos = 7
editor\cycle_case!
assert.equals 'hello Wörld', buffer.text

changes mixed case word to all lowercase

buffer.text = 'hello WörLd'
editor.cursor.pos = 7
editor\cycle_case!
assert.equals 'hello wörld', buffer.text
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/highlight_spec.html b/site/source/versions/0.3/doc/spec/ui/highlight_spec.html deleted file mode 100644 index 3c7484007..000000000 --- a/site/source/versions/0.3/doc/spec/ui/highlight_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.highlight - - - - - - - -
-
- -
- -

howl.ui.highlight

indicator_on = (buffer, pos, number) ->
  b_pos = buffer.text\byte_offset pos
  on = buffer.sci\indicator_all_on_for b_pos - 1
  return on and bit.band(on, number + 1) != 0

.set_for_buffer(sci, buffer) initializes any previously used buffer highlights

sci = Scintilla!
buffer = Buffer {}

highlight.define 'highlight_foo', color: '#334455'
number = highlight.number_for 'highlight_foo', buffer
highlight.set_for_buffer sci, buffer

defined_fore = sci\indic_get_fore number
assert.equal defined_fore, '#334455'

.at_pos(buffer, pos) returns a list of the active highlights at pos

highlight.define 'highlight_bar', color: '#334455'
highlight.define 'highlight_foo', color: '#334455'
buffer = Buffer {}
buffer.text = 'hƏllo'
highlight.apply 'highlight_bar', buffer, 1, 4
assert.same highlight.at_pos(buffer, 1), { 'highlight_bar' }
assert.same highlight.at_pos(buffer, 5), { }

.define(name, definition)

defines a highlight

highlight.define 'custom', color: '#334455'
assert.equal highlight.custom.color, '#334455'

automatically redefines the highlight in any existing sci

highlight.define 'custom', color: '#334455'

sci = Scintilla!
buffer = Buffer {}, sci
number = highlight.number_for 'custom', buffer

highlight.define 'custom', color: '#665544'

set_fore = buffer.sci\indic_get_fore number
assert.equal set_fore, '#665544'

define_default(name, definition)

defines the highlight only if it is not already defined

highlight.define_default 'new_one', color: '#334455'
assert.equal highlight.new_one.color, '#334455'
highlight.define_default 'new_one', color: '#101010'
assert.equal highlight.new_one.color, '#334455'

.apply(name, buffer, pos, length)

activates the highlight for the specified range

buffer = Buffer {}
buffer.text = 'hƏllo'
highlight.define 'custom', color: '#334455'
number = highlight.number_for 'custom', buffer
highlight.apply 'custom', buffer, 2, 2

assert.is_false indicator_on buffer, 1, number
assert.is_true indicator_on buffer, 2, number
assert.is_true indicator_on buffer, 3, number
assert.is_false indicator_on buffer, 4, number

.number_for(name, buffer, sci)

automatically assigns an indicator number and defines the highlight in sci

buffer = Buffer {}, Scintilla!
highlight.define 'my_highlight_a', color: '#334455'
highlight.define 'my_highlight_b', color: '#334455'
highlight_num = highlight.number_for 'my_highlight_a', buffer
set_fore = buffer.sci\indic_get_fore highlight_num
assert.equal set_fore, '#334455'
assert.is_not.equal highlight.number_for('my_highlight_b', buffer), highlight_num

remembers the highlight number used for a particular highlight

buffer = Buffer {}
highlight.define 'got_it', color: '#334455'
highlight_num = highlight.number_for 'got_it', buffer
highlight_num2 = highlight.number_for 'got_it', buffer
assert.equal highlight_num2, highlight_num

raises an error if the number of highlights are exhausted

buffer = Buffer {}

for i = 1, Scintilla.INDIC_MAX + 2
  highlight.define 'my_highlight' .. i, color: '#334455'

assert.raises 'highlights exceeded', ->
  for i = 1, Scintilla.INDIC_MAX + 2
    highlight.number_for 'my_highlight' .. i, buffer

raises an error if the highlight is not defined

assert.raises 'Could not find highlight', -> highlight.number_for 'mystery highlight', {}

.remove_all(name, buffer)

removes all highlights with <name> in <buffer>

highlight.define 'foo', color: '#334455'
buffer = Buffer {}
buffer.text = 'ʘne twʘ'
highlight.apply 'foo', buffer, 1, 3
highlight.apply 'foo', buffer, 5, 3
highlight.remove_all 'foo', buffer
assert.same highlight.at_pos(buffer, 1), { }
assert.same highlight.at_pos(buffer, 4), { }
assert.same highlight.at_pos(buffer, 5), { }
assert.same highlight.at_pos(buffer, 8), { }

does nothing when no highlights have been set

highlight.define 'foo', color: '#334455'
buffer = Buffer {}
buffer.text = 'one two'
highlight.remove_all 'foo', buffer

.remove_in_range(name, buffer, start_pos, end_pos)

removes all highlights with <name> in <buffer> in the range specified (inclusive)

highlight.define 'foo', color: '#334455'
buffer = Buffer {}
buffer.text = 'ʘne twʘ'
highlight.apply 'foo', buffer, 1, 3
highlight.apply 'foo', buffer, 5, 3
highlight.remove_in_range 'foo', buffer, 4, 7
assert.same highlight.at_pos(buffer, 3), { 'foo' }
assert.same highlight.at_pos(buffer, 5), { }
assert.same highlight.at_pos(buffer, 8), { }

does nothing when no highlights have been set

highlight.define 'foo', color: '#334455'
buffer = Buffer {}
buffer.text = 'ʘne twʘ'
highlight.remove_in_range 'foo', buffer, 1, #buffer
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/list_widget_spec.html b/site/source/versions/0.3/doc/spec/ui/list_widget_spec.html deleted file mode 100644 index e73a8126e..000000000 --- a/site/source/versions/0.3/doc/spec/ui/list_widget_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.ListWidget - - - - - - - -
-
- -
- -

howl.ui.ListWidget

local list, buf

before_each ->
  list = ListWidget -> {}
  list.max_height = math.huge
  list\show!
  buf = list.text_widget.buffer

shows empty list until update() is called

list = ListWidget -> {'one', 'two', 'three'}
list\show!
assert.equal '(no items)', list.text_widget.buffer.text
list\update!
assert.not_equal '(no items)', list.text_widget.buffer.text

shows single column items, each on one line

list.matcher = -> {'one', 'two', 'three'}
list\update!
assert.equal 'one  \ntwo  \nthree\n', buf.text

allows items to be Chunks

source_buf = Buffer!
source_buf.text = 'source'
chunk = source_buf\chunk 1, 6
list.matcher = -> { chunk }
list\update!
assert.equal 'source\n', buf.text

shows multi column items each on one line, in separate columns

list.matcher = -> {
  {'first', 'item one'},
  {'second', 'item two'}
}
list.columns = { {}, {} }
list\update!
assert.equal [[
first  item one
second item two
]], buf.text

shows "(no items)" for an empty list

list.matcher = -> {}
list\update!
assert.equal '(no items)', buf.text

shows headers, if given, above the items

list.matcher = -> { {'first', 'item one'} }
list.columns = { {header: 'Header 1'}, {header: 'Header 2'} }
list\update!
assert.equal [[
Header 1 Header 2
first    item one
]], buf.text

all properties can be changed after initial assignment

list.matcher = -> { 'one', 'two' }
list\update!
assert.equal buf.text, 'one\ntwo\n'
list.matcher = -> { 'three', 'four' }
list\update!
assert.equal buf.text, 'three\nfour \n'

list.matcher = -> { { 'three', 'four' } }
list\update!
list.columns = { { header: 'Header 1' }, { header: 'Header 2' } }
list\update!
assert.equal [[
Header 1 Header 2
three    four    ]] .. '\n', buf.text

(matching)

shows matching items only, when update(match_text) called

list.matcher = Matcher {'one', 'twö', 'three'}
list\update 'o'
assert.equal 'one\n', buf.text

styles matching parts of text with list_highlight

list.matcher = Matcher {'one', 'twö', 'three'}
list\update 'ne'
assert.equal 'one\n', buf.text
hstyle = style.at_pos(buf, 1)
assert.not_equal 'list_highlight', hstyle
hstyle = style.at_pos(buf, 2)
assert.equal 'list_highlight', hstyle
hstyle = style.at_pos(buf, 3)
assert.equal 'list_highlight', hstyle

(when `never_shrink:` is not provided)

shrinks the height while matching

list.matcher = Matcher {'one', 'twö', 'three'}
list\update!
height = list.height
assert.not_nil height

list\update 'o'
assert.equal height / 3, list.height

(when `never_shrink: true` is provided)

does not shrink the height while matching

list = ListWidget Matcher({'one', 'twö', 'three'}),
  never_shrink: true
list\show!
list\update!
height = list.height
assert.not_nil height

list\update 'o'
assert.equal height, list.height

(when .max_height is set)

shows only up to max_height pixels

list.matcher = -> {'one', 'two', 'three'}
list.max_height = 2 * get_row_height(list)
list\update!
assert.equal 2 * get_row_height(list), list.height
assert.match buf.text, 'one'
assert.is_not.match buf.text, 'two'

list.max_height = math.huge
list\update!
assert.equal 'one  \ntwo  \nthree\n', buf.text

errors if height is insufficient to show at least one item

list.matcher = -> {'one', 'two' }
list.max_height = get_row_height(list)
assert.raises 'insufficient height', -> list\update!

it takes headers into account when set

list.matcher = -> {'one', 'two', 'three'}
list.columns = { {header:'Takes up one line' } }
list.max_height = 3 * get_row_height(list)
list\update!
assert.match buf.text, 'one'
assert.is_not.match buf.text, 'two'

displays info about the currently shown items

list.matcher = -> {'one', 'two', 'three'}
list.max_height = 2 * get_row_height(list)
list\update!
assert.match buf.text, 'showing 1 to 1 out of 3'

(when .min_height is set)

is ignored when the list is bigger than the value

list.matcher = -> {'one', 'two', 'three'}
list.min_height = 1  -- pixel
list\update!
assert.equals 3, list.height / get_row_height(list)

adds lines to ensure the given value

list.matcher = -> {'one'}
list.min_height = 3 * get_row_height(list)
list\update!
assert.equals 'one\n~\n~\n', buf.text

sets .filler_text for each filler line if specified

list.matcher = -> {'one'}
list.opts.filler_text = '##'
list.min_height = 2 * get_row_height(list)
list\update!
assert.equals 'one\n##\n', buf.text

.height

is set to the number of pixels used for displaying the list

list.matcher = -> {'one', 'two', 'three'}
list\update!
assert.equal 3 * get_row_height(list), list.height

includes headers

list.matcher = -> {'one', 'two', 'three'}
list.columns = { { header: 'Header 1' } }
list\update!
assert.equal 4 * get_row_height(list), list.height

(when items are not strings)

automatically converts items to strings using tostring before displaying

list.matcher = -> { 1, 2 }
list\update!
assert.equal '1\n2\n', buf.text

the selection is still the raw item

list.matcher = -> { 57, 59 }
list\update!
assert.equal list.selection, 57

styling

headers are styled using the list_header style

list.matcher = -> { { 'one' } }
list.columns = { { header: 'Header 1' } }
list\update!
header_style = style.at_pos(buf, 1)
assert.equal 'list_header', header_style

columns are styled using the styles specified in .columns[i].style

list.matcher = -> { { 'first', 'second' } }
list.columns = { { style: 'whitespace'}, { style: 'identifier' } }
list\update!
assert.equal style.at_pos(buf, 1), 'whitespace'
assert.equal style.at_pos(buf, 7), 'identifier'

(selection)

before_each ->
  list.matcher = -> { 'one', 'two', 'three' }
  list\update!

selects the first item by default

assert.equal 'one', list.selection

.selection is nil for an empty list

list.matcher = -> {}
list\update!
assert.is_nil list.selection

highlights the selected item with list_selection

assert.same { 'list_selection' }, highlight.at_pos(buf, 1)
assert.same {}, highlight.at_pos(buf, buf.lines[2].start_pos)

adjusts highlight when headers present

list.columns = { { header: 'Head' } }
list\update!
assert.same {}, highlight.at_pos(buf, 1)
assert.same { 'list_selection' }, highlight.at_pos(buf, buf.lines[2].start_pos)

pads lines if neccessary to achieve a uniform selection highlight

assert.equal 5, #buf.lines[1]
assert.equal 5, #buf.lines[3]

.selection = <item>

causes <item> to be selected

list.selection = 'two'
assert.equal list.selection, 'two'

raises an error if <item> can not be found

assert.raises 'not found', -> list.selection = 'five'

highlights the new selection and clears any old highlight

list.selection = 'one'
assert.same highlight.at_pos(buf, 1), { 'list_selection' }
list.selection = 'two'
assert.same { 'list_selection' }, highlight.at_pos(buf, buf.lines[2].start_pos)
assert.same {}, highlight.at_pos(buf, 1)

scrolls the list if needed

list.max_height = 2 * get_row_height(list)
list\update!
list.selection = 'three'
assert.match buf.text, 'three'

select_next()

selects the next item

list\select_next!
assert.equal 'two', list.selection

selects the first item if at the end of the list

list.selection = 'three'
list\select_next!
assert.equal 'one', list.selection

scrolls to the item if neccessary

list.max_height = 2 * get_row_height(list)
list\update!
list\select_next!
assert.equal 'two', list.selection
assert.match buf.text, 'two'

select_prev()

selects the previous item

list.selection = 'three'
list\select_prev!
assert.equal 'two', list.selection

selects the last item if at the start of the list

list\select_prev!
assert.equal list.selection, 'three'

scrolls to the item if neccessary

list.max_height = 2 * get_row_height(list)
list\update!
list.selection = 'three'
list\select_prev!
assert.equal 'two', list.selection
assert.match buf.text, 'two'

next_page()

scrolls to the next page

list.matcher = -> {'one', 'two', 'three'}
list.max_height = 2 * get_row_height(list)
list\update!
list\next_page!
assert.equal 2, list.offset

scrolls to the first page if at the end of the list

list.matcher = -> {'one', 'two', 'three'}
list.max_height = 2 * get_row_height(list)
list\update!
list.selection = 'three'
list\next_page!
assert.equal 1, list.offset

prev_page()

scrolls to the previous page

list.matcher = -> {'one', 'two', 'three'}
list.max_height = 2 * get_row_height(list)
list\update!
list.selection = 'three'
list\prev_page!
assert.equal list.offset, 2

scrolls to the last page if at the start of the list

list.matcher = -> {'one', 'two', 'three'}
list.max_height = 2 * get_row_height(list)
list\update!
list\prev_page!
assert.equal list.offset, 3

(when reverse is true)

local rlist, rbuf

before_each ->
  matcher = -> { 'one', 'two', 'three' }
  rlist = ListWidget matcher, reverse: true
  rlist\show!
  rbuf = rlist.text_widget.buffer
  rlist\update!

shows the items in reverse order

assert.equal 'three\ntwo  \none  \n', rbuf.text

selects the last item by default

assert.equal 'one', rlist.selection
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/markup/howl_spec.html b/site/source/versions/0.3/doc/spec/ui/markup/howl_spec.html deleted file mode 100644 index feca6e3be..000000000 --- a/site/source/versions/0.3/doc/spec/ui/markup/howl_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.markup.howl - - - - - - - -
-
- -
- -

howl.ui.markup.howl

returns a StyledText instance with empty styles if no markup is present

assert.same StyledText('foo', {}), m 'foo'

returns a StyledText instance with styles for howl_markup

expected = StyledText 'foo', { 1, 'number', 4 }
assert.same expected, m '<number>foo</number>'

allows the end tag to be simplified

expected = StyledText 'foobar', { 1, 'number', 4 }
assert.same expected, m '<number>foo</>bar'

handles multiple howl_markups

expected = StyledText 'hi my prompt!', {
  4, 'string', 6,
  7, 'error', 13,
}
assert.same expected, m 'hi <string>my</string> <error>prompt</>!'

content can contain newlines

expected = StyledText 'x\nx', {
  1, 'string', 4
}
assert.same expected, m "<string>x\nx</string>"
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/markup/terminal_spec.html b/site/source/versions/0.3/doc/spec/ui/markup/terminal_spec.html deleted file mode 100644 index 63d3fa6a6..000000000 --- a/site/source/versions/0.3/doc/spec/ui/markup/terminal_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.markup.terminal - - - - - - - -
-
- -
- -

howl.ui.markup.terminal

returns a StyledText instance with empty styles if no markup is detected

assert.same StyledText('foo', {}), m 'foo'

(ANSI escape sequences)

handles bold

expected = StyledText 'foo', { 1, 'ansi_bold', 4 }
assert.same expected, m '\027[1mfoo\027[0m'

handles italic

expected = StyledText 'foo', { 1, 'ansi_italic', 4 }
assert.same expected, m '\027[3mfoo\027[0m'

handles underline

expected = StyledText 'foo', { 1, 'ansi_underline', 4 }
assert.same expected, m '\027[4mfoo\027[0m'

handles background colors

expected = StyledText 'foo', { 1, 'ansi_red', 4 }
assert.same expected, m '\027[31mfoo\027[0m'

handles background colors

expected = StyledText 'foo', { 1, 'ansi_on_red', 4 }
assert.same expected, m '\027[41mfoo\027[0m'

handles combined foreground and background colors

expected = StyledText 'foo', { 1, 'ansi_green_on_red', 4 }
assert.same expected, m '\027[41;32mfoo\027[0m'

styles remain in effect until resetted

expected = StyledText 'foobar', {
  1, 'ansi_on_red', 4
  4, 'ansi_green_on_red', 7
}
assert.same expected, m '\027[41mfoo\027[32mbar\027[0m'

handles empty reset sequences

expected = StyledText 'foobar', { 1, 'ansi_italic', 4 }
assert.same expected, m '\027[3mfoo\027[mbar'

handles prematurely terminated sequences

expected = StyledText 'foo', { 1, 'ansi_red', 4 }
assert.same expected, m '\027[31mfoo'

handles foreground color resetting

expected = StyledText 'foobar', { 1, 'ansi_red', 4 }
assert.same expected, m '\027[31mfoo\027[39mbar'

handles background color resetting

expected = StyledText 'foobar', { 1, 'ansi_on_red', 4 }
assert.same expected, m '\027[41mfoo\027[49mbar'

skips over unhandled escape sequences

expected = StyledText 'foo', {}
assert.same expected, m '\027[2,2Hfoo'

ignores unhandled graphic parameters

expected = StyledText 'foo', { 1, 'ansi_red', 4 }
assert.same expected, m '\027[31;5;6mfoo\027[m'

(backspace characters (BS))

deletes back properly

expected = StyledText 'fo', {}
assert.same expected, m 'foo\008'

deletes a UTF-8 code point back, and not a byte

expected = StyledText 'åä', {}
assert.same expected, m 'åäö\008'

BS at the start of the text is left alone

expected = StyledText '\008foo', {}
assert.same expected, m '\008foo'

updates the styling accordingly

expected = StyledText 'fobar', { 1, 'ansi_red', 3 }
assert.same expected, m '\027[31mfoo\027[m\008bar'

removes any left empty styling

expected = StyledText 'fobar', { }
assert.same expected, m 'fo\027[31mo\027[m\008bar'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/searcher_spec.html b/site/source/versions/0.3/doc/spec/ui/searcher_spec.html deleted file mode 100644 index 3b25a2c6e..000000000 --- a/site/source/versions/0.3/doc/spec/ui/searcher_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.Searcher - - - - - - - -
-
- -
- -

howl.ui.Searcher

buffer = nil
editor = Editor Buffer {}
searcher = editor.searcher
cursor = editor.cursor

before_each ->
  buffer = Buffer {}
  editor.buffer = buffer

after_each -> searcher\cancel!

cancel() moves the cursor back to the original position

buffer.text = 'hello!'
cursor.pos = 1
searcher\forward_to 'll'
searcher\cancel!
assert.equal 1, cursor.pos

repeat_last() repeats the last search in the last direction

buffer.text = 'hellö wörld'
cursor.pos = 1

searcher\forward_to 'ö'
searcher\commit!
assert.equal 5, cursor.pos
searcher\repeat_last!
assert.equal 8, cursor.pos

cursor.pos = 11
searcher\backward_to 'ö'
searcher\commit!
assert.equal 8, cursor.pos
searcher\repeat_last!
assert.equal 5, cursor.pos

.active is true if the searcher is currently active

assert.is_false searcher.active
searcher\forward_to 'o'
assert.is_true searcher.active
searcher\cancel!
assert.is_false searcher.active

forward_to(string)

moves the cursor to the next occurrence of <string>

buffer.text = 'hellö\nworld!'
cursor.pos = 1
searcher\forward_to 'l'
assert.equal 3, cursor.pos
searcher\forward_to 'ld'
assert.equal 10, cursor.pos

highlights the match with "search"

buffer.text = 'hellö\nworld!'
cursor.pos = 1
searcher\forward_to 'lö'
assert.same { 'search' }, highlight.at_pos buffer, 4
assert.same { 'search' }, highlight.at_pos buffer, 5
assert.not_same { 'search' }, highlight.at_pos buffer, 6

matches at the current position

buffer.text = 'no means no'
cursor.pos = 1
searcher\forward_to 'no'
assert.equal 1, cursor.pos

handles growing match from empty

buffer.text = 'no means no'
cursor.pos = 1
searcher\forward_to ''
assert.equal 1, cursor.pos
searcher\forward_to 'n'
assert.equal 1, cursor.pos
searcher\forward_to 'no'
assert.equal 1, cursor.pos

does not move the cursor when there is no match

buffer.text = 'hello!'
cursor.pos = 1
searcher\forward_to 'foo'
assert.equal 1, cursor.pos

next()

moves to the next match

buffer.text = 'aaaa'
cursor.pos = 1
searcher\forward_to 'a'
assert.equal 1, cursor.pos
searcher\next!
assert.equal 2, cursor.pos
searcher\next!
assert.equal 3, cursor.pos

previous()

moves to the previous match

buffer.text = 'aaaa'
cursor.pos = 4
searcher\forward_to 'a'
assert.equal 4, cursor.pos
searcher\previous!
assert.equal 3, cursor.pos
searcher\previous!
assert.equal 2, cursor.pos

backward_to(string)

moves the cursor to the previous occurrence of <string>

buffer.text = 'hellö\nworld!'
cursor.pos = 11
searcher\backward_to 'l'
assert.equal 10, cursor.pos
searcher\backward_to 'lö'
assert.equal 4, cursor.pos

handles search term growing from empty

buffer.text = 'aaaaaaaa'
cursor.pos = 5
searcher\backward_to ''
assert.equal 5, cursor.pos
searcher\backward_to 'a'
assert.equal 4, cursor.pos
searcher\backward_to 'aa'
assert.equal 4, cursor.pos

skips any matches at the current position by default

buffer.text = 'aaaaaaaa'
cursor.pos = 5
searcher\backward_to 'a'
assert.equal 4, cursor.pos

finds matches that overlap with cursor

buffer.text = 'ababababa'
cursor.pos = 4
searcher\backward_to 'baba'
assert.equal 2, cursor.pos

does not skip any matches at the current position if the searcher is active

buffer.text = 'abaaaaab'
cursor.pos = 8
searcher\backward_to 'a'
assert.equal 7, cursor.pos
searcher\backward_to 'ab'
assert.equal 7, cursor.pos

does not move the cursor when there is no match

buffer.text = 'hello!'
cursor.pos = 3
searcher\backward_to 'f'
assert.equal 3, cursor.pos

next()

moves to the next match

buffer.text = 'aaaa'
cursor.pos = 4
searcher\backward_to 'a'
searcher\next!
assert.equal 4, cursor.pos

previous()

moves to the previous match

buffer.text = 'aaaa'
cursor.pos = 3
searcher\backward_to 'a'
searcher\next!
searcher\previous!
assert.equal 2, cursor.pos

forward_to(string, "word")

moves the cursor to the start of the current word

buffer.text = 'hello helloo hello'
cursor.pos = 2
searcher\forward_to 'hello', 'word'
assert.equal 1, cursor.pos

highlights current word with "search" and other word matches with "search_secondary"

buffer.text = 'hello hola hello hola hello'
cursor.pos = 13
searcher\forward_to 'hello', 'word'
assert.same { 'search' }, highlight.at_pos buffer, 12
assert.same { 'search' }, highlight.at_pos buffer, 16
assert.same { 'search_secondary' }, highlight.at_pos buffer, 1
assert.same { 'search_secondary' }, highlight.at_pos buffer, 23

highlights overlapping matches correctly

buffer.text = 'aaa'
cursor.pos = 2
searcher\forward_to 'aa'
assert.equal 2, cursor.pos
assert.same { 'search_secondary' }, highlight.at_pos buffer, 1
assert.same { 'search_secondary', 'search' }, highlight.at_pos buffer, 2
assert.same { 'search' }, highlight.at_pos buffer, 3

does not move the cursor when there is no match

buffer.text = 'hello!'
cursor.pos = 1
searcher\forward_to 'foo', 'word'
assert.equal 1, cursor.pos

next()

moves to the next word match

buffer.text = 'hello helloo hello'
cursor.pos = 1
searcher\forward_to 'hello', 'word'
searcher\next!
assert.equal 14, cursor.pos

previous()

moves to the previous word match

buffer.text = 'hello helloo hello'
cursor.pos = 1
searcher\forward_to 'hello', 'word'
searcher\next!
searcher\previous!
assert.equal 1, cursor.pos

backward_to(string, "word")

moves the cursor to the previous occurrence of word match <string>

buffer.text = 'hello helloo hello'
cursor.pos = 14
searcher\backward_to 'hello', 'word'
assert.equal 1, cursor.pos

skips match at the current position by default

buffer.text = 'no means no'
cursor.pos = 9
searcher\backward_to 'no', 'word'
assert.equal 1, cursor.pos

does not move the cursor when there is no match

buffer.text = 'hello!'
cursor.pos = 2
searcher\backward_to 'foo', 'word'
assert.equal 2, cursor.pos

handles substring match at start of file gracefully

buffer.text = 'abcd  abc'
cursor.pos = 6
searcher\backward_to 'abc', 'word'
assert.equal 7, cursor.pos

next()

moves to the next word match

buffer.text = 'hello helloo hello'
cursor.pos = 14
searcher\backward_to 'hello', 'word'
searcher\next!
assert.equal 14, cursor.pos

previous()

moves to the previous word match

buffer.text = 'hello helloo hello'
cursor.pos = 14
searcher\backward_to 'hello', 'word'
searcher\next!
searcher\previous!
assert.equal 1, cursor.pos
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/selection_spec.html b/site/source/versions/0.3/doc/spec/ui/selection_spec.html deleted file mode 100644 index 40abf8b12..000000000 --- a/site/source/versions/0.3/doc/spec/ui/selection_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.Selection - - - - - - - -
-
- -
- -

howl.ui.Selection

buffer = Buffer {}
editor = Editor buffer
selection = editor.selection
cursor = editor.cursor
window = Gtk.OffscreenWindow!
window\add editor\to_gobject!
window\show_all!

before_each ->
  buffer.text = text
  selection.sci\set_empty_selection 0

set(anchor, pos) sets the anchor and cursor at the same time

selection\set 1, 5
assert.equal 'Liñe', selection.text

select(anchor, pos) adjusts the selection to include the specified range

selection\select 1, 4
assert.equal 5, selection.cursor
assert.equal 'Liñe', selection.text

selection\select 4, 2
assert.equal 5, selection.anchor
assert.equal 'iñe', selection.text

select_all() adjusts the selection to include the entire buffer

selection\select_all!
assert.equal 1, selection.anchor
assert.equal text.ulen + 1, selection.cursor

.empty returns whether any selection exists

assert.is_true selection.empty
selection\set 1, 3
assert.is_false selection.empty

range() returns the [start, stop) range of the selection in ascending order

selection\set 2, 5
start, stop = selection\range!
assert.equal 2, start
assert.equal 5, stop

selection\set 5, 2
start, stop = selection\range!
assert.equal 2, start
assert.equal 5, stop

.anchor

returns the current position if nothing is selected

cursor.pos = 3
assert.equal 3, selection.anchor

returns the start position of the selection with a selection active

selection\set 2, 5
assert.equal 2, selection.anchor

setting it to <pos> sets the selection to the text range [pos..<cursor>)

cursor.pos = 3
selection.anchor = 1
assert.equal 1, selection.anchor
assert.equal 'Li', selection.text

.cursor

returns the current position if nothing is selected

cursor.pos = 3
assert.equal 3, selection.cursor

returns the end position of the selection with a selection active

selection\set 2, 5
assert.equal 5, selection.cursor

selection.anchor = 3
selection.cursor = 5
assert.equal 5, selection.cursor
assert.equal 'ñe', selection.text

.persistent

causes the selection to be extended with movement when true

cursor.pos = 1
selection.persistent = true
cursor\down!
assert.equal 'Liñe 1 ʘf tƏxt\n', selection.text

remove

removes the selection

selection\set 2, 5
selection\remove!
assert.is_true selection.empty

does not remove the selected text

selection\set 2, 5
selection\remove!
assert.equal text, buffer.text

does not change the cursor position

selection\set 2, 5
selection\remove!
assert.equal 5, cursor.pos

cut

removes the selected text

selection\set 1, 5
selection\cut!
assert.equal ' 1 ʘf tƏxt', buffer.lines[1].text

removes the selection

selection\set 2, 5
selection\cut!
assert.is_true selection.empty

clears the persistent flag

selection\set 1, 5
selection.persistent = true
selection\cut!
assert.is_false selection.persistent

pushes the selection to the clipboard, with any options as specified

selection\set 1, 2
selection\cut!

assert.equal 'L', clipboard.current.text

selection\set 1, 2
selection\cut whole_lines: true
assert.equal true, clipboard.current.whole_lines

selection\set 1, 3
selection\cut {}, to: 'abc'
assert.equal 'ñe', clipboard.registers.abc.text

signals "selection-cut"

with_signal_handler 'selection-cut', nil, (handler) ->
  selection\set 1, 5
  selection\cut!
  assert.spy(handler).was_called!

(clip_options = nil, clipboard_options = nil)

removes the selection

selection\set 1, 5
selection\copy!
assert.is_true selection.empty

clears the persistent flag

selection\set 1, 5
selection.persistent = true
selection\copy!
assert.is_false selection.persistent

pushes the selection to the clipboard, with any options as specified

selection\set 1, 5
selection\copy!

assert.equal 'Liñe', clipboard.current.text

selection\set 1, 5
selection\copy whole_lines: true
assert.equal true, clipboard.current.whole_lines

selection\set 1, 4
selection\copy {}, to: 'abc'
assert.equal 'Liñ', clipboard.registers.abc.text

signals "selection-copied"

with_signal_handler 'selection-copied', nil, (handler) ->
  selection\set 1, 5
  selection\copy!
  assert.spy(handler).was_called!

.text

returns nil if nothing is selected

assert.is_nil selection.text

returns the currently selected text when the selection is not empty

selection\set 1, 3
assert.equal 'Li', selection.text

.text = <text>

replaces the selection with <text> and removes the selection

selection\set 1, 3
selection.text = 'Shi'
assert.equal 'Shiñe 1 ʘf tƏxt', buffer.lines[1].text
assert.is_true selection.empty

raises an error if the selection is empty

assert.raises 'empty', -> selection.text = 'Yowser!'

when .includes_cursor is set to true

before_each -> selection.includes_cursor = true
after_each -> selection.includes_cursor = false

select(anchor, pos) adjusts pos if needed to only point at the end of selection

selection\select 1, 4
assert.equal 4, selection.cursor
assert.equal 'Liñe', selection.text

selection\select 4, 2
assert.equal 5, selection.anchor
assert.equal 'iñe', selection.text

.text includes the current character

selection\set 1, 3
assert.equal 'Liñ', selection.text

.text = <text> replaces the current character as well

selection\set 1, 2
selection.text = 'Shi'
assert.equal 'Shiñe 1 ʘf tƏxt', buffer.lines[1].text

cut() removes the current character as well

selection\set 1, 5
selection\cut!
assert.equal '1 ʘf tƏxt', buffer.lines[1].text

copy() copies the current character as well

selection\set 1, 4
selection\copy!
cursor.column = 1
editor\paste!
assert.equal 'LiñeLiñe 1 ʘf tƏxt', buffer.lines[1].text

.empty is still true if anchor and pos are the same sans the includes_cursor

selection\set 1, 1
assert.is_true selection.empty

selection\set 1, 2
assert.is_false selection.empty

(when the selection ends at a end-of-line character)

before_each ->
  buffer.text = 'liñe1\nline2'
  selection\set 1, 6

the end-of-line character is not included in the selection

assert.equal 'liñe1', selection.text

range()

includes the cursor position if needed

selection\set 2, 5
start, stop = selection\range!
assert.equal 2, start
assert.equal 6, stop

selection\set 5, 2
start, stop = selection\range!
assert.equal 2, start
assert.equal 5, stop

does not include an position after eof however

selection\set #buffer - 1, #buffer + 1
start, stop = selection\range!
assert.equal #buffer - 1, start
assert.equal #buffer + 1, stop
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/style_spec.html b/site/source/versions/0.3/doc/spec/ui/style_spec.html deleted file mode 100644 index b0100c636..000000000 --- a/site/source/versions/0.3/doc/spec/ui/style_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.style - - - - - - - -
-
- -
- -

howl.ui.style

local sci, buffer

before_each ->
    sci = Scintilla!
    buffer = Buffer {}, sci
    style.register_sci sci

styles can be accessed using direct indexing

t = styles: default: color: '#998877'
style.set_for_theme t
assert.equal style.default.color, t.styles.default.color

.name_for(number, buffer, sci) returns the style name for number

assert.equal style.name_for(5, {}), 'keyword' -- default keyword number

style.define 'whats_in_a_name', color: '#334455'
style_num = style.number_for 'whats_in_a_name', buffer
assert.equal style.name_for(style_num, buffer), 'whats_in_a_name'

.set_for_buffer(sci, buffer) initializes any previously used buffer styles

sci2 = Scintilla!
style.register_sci sci2

style.define 'style_foo', color: '#334455'
prev_number = style.number_for 'style_foo', buffer
style.set_for_buffer sci2, buffer

defined_fore = sci2\style_get_fore prev_number
assert.equal defined_fore, '#334455'

new_number = style.number_for 'style_foo', buffer
assert.equal new_number, prev_number

.at_pos(buffer, pos) returns name and style definition at pos

style.define 'stylish', color: '#101010'
buffer = ActionBuffer!
buffer\insert 'hƏllo', 1, 'keyword'
buffer\insert 'Bačon', 6, 'stylish'

name, def = style.at_pos(buffer, 5)
assert.equal name, 'keyword'
assert.same def, style.keyword

name, def = style.at_pos(buffer, 6)
assert.equal name, 'stylish'
assert.same def, style.stylish

.define(name, definition)

allows defining custom styles

style.define 'custom', color: '#334455'
assert.equal style.custom.color, '#334455'

automatically redefines the style in any existing sci

style.define 'keyword', color: '#334455'
style.define 'custom', color: '#334455'

custom_number = style.number_for 'custom', buffer
keyword_number = style.number_for 'keyword', buffer

style.define 'keyword', color: '#665544'
style.define 'custom', color: '#776655'

keyword_fore = sci\style_get_fore keyword_number
assert.equal '#665544', keyword_fore

custom_fore = sci\style_get_fore custom_number
assert.equal '#776655', custom_fore

allows specifying font size for a style as an offset spec from "font_size"

style.define 'larger_style', font: size: 'larger'
style_number = style.number_for 'larger_style', buffer
font_size = sci\style_get_size style_number
assert.is_true font_size > config.font_size

allows aliasing styles using a string as <definition>

style.define 'target', color: '#beefed'
style.define 'alias', 'target'
style_number = style.number_for 'alias', buffer
assert.equal '#beefed', sci\style_get_fore style_number

the actual style used is based upon the effective default style

style.define 'default', background: '#112233'
style.define 'other_default', background: '#111111'
style.define 'custom', color: '#beefed'

sci2 = Scintilla!
buffer2 = Buffer {}, sci2
style.register_sci sci2, 'other_default'

style_number = style.number_for 'custom', buffer
assert.equal '#112233', sci\style_get_back style_number

style_number = style.number_for 'custom', buffer2
assert.equal '#111111', sci2\style_get_back style_number

redefining a default style causes other styles to be rebased upon that

style.define 'own_style', color: '#334455'

custom_number = style.number_for 'own_style', buffer
default_number = style.number_for 'default', buffer

style.define 'default', background: '#998877'

custom_back = sci\style_get_back custom_number
assert.equal '#998877', custom_back

custom_fore = sci\style_get_fore custom_number
assert.equal '#334455', custom_fore

define_default(name, definition)

defines the style only if it is not already defined

style.define_default 'preset', color: '#334455'
assert.equal style.preset.color, '#334455'

style.define_default 'preset', color: '#667788'
assert.equal style.preset.color, '#334455'

.number_for(name, buffer [, base])

returns the assigned style number for name in sci

assert.equal style.number_for('keyword'), 5 -- default keyword number

automatically assigns a style number and defines the style in scis if necessary

style.define 'my_style_a', color: '#334455'
style.define 'my_style_b', color: '#334455'
style_num = style.number_for 'my_style_a', buffer
set_fore = sci\style_get_fore style_num
assert.equal set_fore, '#334455'
assert.is_not.equal style.number_for('my_style_b', buffer), style_num

remembers the style number used for a particular style

style.define 'got_it', color: '#334455'
style_num = style.number_for 'got_it', buffer
style_num2 = style.number_for 'got_it', buffer
assert.equal style_num2, style_num

raises an error if the number of styles are #exhausted

for i = 1, 255 style.define 'my_style' .. i, color: '#334455'

assert.raises 'Out of style number', ->
  for i = 1, 255 style.number_for 'my_style' .. i, buffer

returns the default style number if the style is not defined

assert.equal style.number_for('foo', {}), style.number_for('default', {})

.register_sci(sci, default_style)

defines the default styles in the specified sci

t = theme.current
t.styles.keyword = color: '#112233'
style.set_for_theme t

sci2 = Scintilla!
buffer2 = Buffer {}, sci2

number = style.number_for 'keyword', buffer2
old = sci2\style_get_fore number
style.register_sci sci2
new = sci2\style_get_fore number
assert.is_not.equal new, old
assert.equal new, t.styles.keyword.color

allows specifying a different default style through <default_style>

t = theme.current
t.styles.keyword = color: '#223344'
style.set_for_theme t

sci2 = Scintilla!
style.register_sci sci2, 'keyword'
def_fore = sci2\style_get_fore style.number_for 'default', {}
def_kfore = sci2\style_get_fore style.number_for 'keyword', {}
assert.equal t.styles.keyword.color, def_fore

(extended styles)

before_each ->
  style.define 'my_base', background: '#112233'
  style.define 'my_style', color: '#334455'

redefining a default style also rebases extended styles

style_num = style.number_for 'my_style', buffer, 'my_base'

assert.is_false sci\style_get_bold style_num
style.define 'default', font: bold: true

assert.is_true sci\style_get_bold style_num

assert.equal '#112233', sci\style_get_back style_num

.number_for(name, buffer, base)

(when base is specified)

automatically defines an extended style based upon the base and specified style

style_num = style.number_for 'my_style', buffer, 'my_base'
set_fore = sci\style_get_fore style_num
set_back = sci\style_get_back style_num
assert.equal set_fore, '#334455'
assert.equal set_back, '#112233'
assert.is_not_nil style['my_base:my_style']

returns the base style if the specified style is not found

style_num = style.number_for 'my_unknown_style', buffer, 'my_base'
assert.equal style.number_for('my_base', buffer), style_num

(.. when <name> itself specifies an extended style)

extracts the base automatically

style.define 'my_other_base', background: '#112244'
style_num = style.number_for 'my_other_base:my_style', buffer
set_fore = sci\style_get_fore style_num
set_back = sci\style_get_back style_num
assert.equal '#334455', set_fore
assert.equal '#112244', set_back
assert.is_not_nil style['my_other_base:my_style']

(.. when one of composing styles is redefined)

updates the extended style definition

style_num = style.number_for 'my_style', buffer, 'my_base'
style.define 'my_base', background: '#222222'
assert.equal '#222222', style['my_base:my_style'].background

style.define 'my_style', color: '#222222'
assert.equal '#222222', style['my_base:my_style'].color

set_fore = sci\style_get_fore style_num
set_back = sci\style_get_back style_num
assert.equal set_fore, '#222222'
assert.equal set_back, '#222222'
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/styled_text_spec.html b/site/source/versions/0.3/doc/spec/ui/styled_text_spec.html deleted file mode 100644 index 3cc2990e9..000000000 --- a/site/source/versions/0.3/doc/spec/ui/styled_text_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.StyledText - - - - - - - -
-
- -
- -

howl.ui.StyledText

has a type of StyledText

assert.equal 'StyledText', typeof StyledText('foo', {})

responds to tostring correctly

assert.equal 'my_text', tostring StyledText('my_text', {})

responds to the length operator (#)

assert.equal 7, #StyledText('my_text', {})

can be concatenated with strings

st = StyledText('foo', {})
assert.equal 'foobar', st .. 'bar'
assert.equal 'barfoo', 'bar' .. st

can be concatenated with StyledText to produce StyledText

st1 = StyledText('foö', {1, 'string', 5})
st2 = StyledText('1234', {1, 'number', 5})
assert.equal StyledText('foö1234', {1, 'string', 5, 5, 'number', 9}), st1 .. st2

defers to the string meta table

st = StyledText('xåäö', {})
assert.equal 'x', st\sub 1, 1
assert.equal 'å', st\usub 2, 2
assert.equal 4, st.ulen

is equal to other StyledText instances with the same values

assert.equal StyledText('foo', {}), StyledText('foo', {})
assert.equal StyledText('foo', {1, 'number', 3}), StyledText('foo', {1, 'number', 3})
assert.not_equal StyledText('fo1', {1, 'number', 3}), StyledText('foo', {1, 'number', 3})
assert.not_equal StyledText('foo', {1, 'number', 2}), StyledText('foo', {1, 'number', 3})

(for_table)

returns a table containing rows padded and newline terminated

assert.equal 'one  \ntwo  \nthree\n', tostring StyledText.for_table {'one', 'two', 'three'}

converts numbers to string

tbl = StyledText.for_table { 33 }
assert.includes tostring(tbl), '33'

(.. when items contain chunks)

pads chunks correctly

buf = Buffer!
buf.text = ' twë '
chunk = buf\chunk 2, 4
tbl = StyledText.for_table {'onë', chunk, 'three'}
assert.equal 'onë  \ntwë  \nthree\n', tostring tbl

preserves chunks styles

buf = howl.ui.ActionBuffer!
buf\append 'hëllo', 'string'
chunk = buf\chunk 1, 5

tbl = StyledText.for_table {chunk, chunk}
assert.equal 'hëllo \nhëllo \n', tostring tbl
assert.same tbl.styles, {1, 'string', 7, 9, 'string', 15}

(.. when column style is provided)

applies column style to text and padding

tbl = StyledText.for_table { 'one', 'twooo' }, { {style: 'string'} }
assert.same tbl.text, 'one  \ntwooo\n'
assert.same tbl.styles, {1, 'string', 4, 4, 'string', 6, 7, 'string', 12}

preserves style for StyledText objects

tbl = StyledText.for_table {'one', StyledText('two', {1, 'string', 4}), 'three'}, {
  { style: 'comment' }
}
assert.same tbl.styles, {1, 'comment', 4, 4, 'comment', 6, 7, 'string', 10, 10, 'comment', 12, 13, 'comment', 18}

(.. when a header is provided)

includes header row

assert.equal 'Header\none   \n', tostring StyledText.for_table { 'one' }, { {header: 'Header'}}

pads header

assert.equal 'Heád      \nfirst item\n', tostring StyledText.for_table { 'first item' }, { {header: 'Heád'}}

styles headers with header_list, including padding

tbl = StyledText.for_table { 'one-long-column' }, { {header: 'Head'} }
assert.same tbl.styles, {1, 'list_header', 16}

(.. when multiple columns are provided)

returns a table containing multi columns rows

assert.equal 'oná   first \ntwo   second\nthree third \n', tostring StyledText.for_table {
  {'oná', 'first'}
  {'two', 'second'}
  {'three', 'third'}
}

columns can each have a header

assert.equal 'Head1 Head2\none   two  \n', tostring StyledText.for_table { {'one', 'two'} }, { {header: 'Head1'}, {header: 'Head2'} }

displays nothing for nil items

assert.equal 'one   a  \n      two\nthree    \n', tostring StyledText.for_table {
  {'one', 'a'}
  {nil, 'two'}
  {'three', nil}
}, { {}, {} }
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/theme_spec.html b/site/source/versions/0.3/doc/spec/ui/theme_spec.html deleted file mode 100644 index dc3e38537..000000000 --- a/site/source/versions/0.3/doc/spec/ui/theme_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.theme - - - - - - - -
-
- -
- -

howl.ui.theme

assigning directly to .current raises an error

File.with_tmpfile (file) ->
  file.contents = serpent.dump spec_theme
  theme.register 'foo', file
  assert.has_errors -> config.current = 'foo'

register(name, file)

adds name to .all

file = File 'test'
theme.register 'test', file
assert.equal theme.all.test, file

raises an error if name is omitted

status, msg = pcall theme.register, nil, File 'foo'
assert.is_false(status)
assert.match msg, 'name'

raises an error if file is omitted

status, msg = pcall theme.register, 'test'
assert.is_false(status)
assert.match msg, 'file'

unregister(name)

removes the theme from all

file = File 'tmp'
theme.register 'tmp', file
theme.unregister 'tmp'
assert.is_nil theme.all.tmp

assigning a new theme to config.theme

logs an error if there's an error loading the theme

File.with_tmpfile (file) ->
  file.contents = "error('cantload')"
  theme.register 'error', file
  config.theme = 'error'
  assert.match log.last_error.message, 'cantload'

assigns the loaded theme to .current and sets .name

File.with_tmpfile (file) ->
  file.contents = serpent.dump spec_theme
  theme.register 'foo', file
  config.theme = 'foo'
  expected = theme_copy!
  expected.name = 'foo'
  assert.same theme.current, expected

emits a theme-changed event with the newly set theme

File.with_tmpfile (file) ->
  file.contents = serpent.dump spec_theme
  theme.register 'alert', file
  handler = spy.new -> true
  signal.connect 'theme-changed', handler
  config.theme = 'alert'
  signal.disconnect 'theme-changed', handler
  expected = theme_copy!
  expected.name = 'alert'
  assert.spy(handler).was_called_with theme: expected

does not propagate global assignments to the global environment

File.with_tmpfile (file) ->
  file.contents = 'spec_global = "noo!"\n' .. serpent.dump spec_theme
  theme.register 'foo', file
  config.theme = 'foo'
  assert.is_nil spec_global

allows the use of named colors

File.with_tmpfile (file) ->
  theme_string = serpent.dump spec_theme
  theme_string = theme_string\gsub '"#777777"', 'violet' -- footer.color
  file.contents = theme_string
  theme.register 'colors', file
  config.theme = 'colors'
  assert.equal theme.current.editor.footer.color, '#ee82ee'

widget background support

set_theme_with_background = (color, style = 'default') ->
  File.with_tmpfile (file) ->
    the_theme = theme_copy!
    the_theme.styles[style].background = color
    file.contents = serpent.dump the_theme
    theme.register 'with_background', file
    config.theme = 'with_background'

register_background_widget(widget, style)

overrides the widget's background with the current theme background

widget = Gtk.EventBox!
set_theme_with_background 'red'
theme.register_background_widget widget
bg = widget.style_context\get_background_color Gtk.STATE_FLAG_NORMAL
assert.same { 1, 0, 0 }, { bg.red, bg.green, bg.blue }

updates the widget's background whenever the theme changes

widget = Gtk.EventBox!
set_theme_with_background 'red'
theme.register_background_widget widget
set_theme_with_background 'blue'
bg = widget.style_context\get_background_color Gtk.STATE_FLAG_NORMAL
assert.same { 0, 0, 1 }, { bg.red, bg.green, bg.blue }

(when <style> is specified)

uses the named style for the background if possible

widget = Gtk.EventBox!
set_theme_with_background '#00ff00', 'popup'
theme.register_background_widget widget, 'popup'
bg = widget.style_context\get_background_color Gtk.STATE_FLAG_NORMAL
assert.same { 0, 1, 0 }, { bg.red, bg.green, bg.blue }

falls back to the default style if the specified style is unavailable

widget = Gtk.EventBox!
set_theme_with_background 'red'
theme.register_background_widget widget, 'popup'
bg = widget.style_context\get_background_color Gtk.STATE_FLAG_NORMAL
assert.same { 1, 0, 0 }, { bg.red, bg.green, bg.blue }

unregister_background_widget(widget)

causes the widget to be excluded from theme auto-updates

widget = Gtk.EventBox!
set_theme_with_background 'red'
theme.register_background_widget widget
theme.unregister_background_widget widget
set_theme_with_background 'blue'
bg = widget.style_context\get_background_color Gtk.STATE_FLAG_NORMAL
assert.same { 1, 0, 0 }, { bg.red, bg.green, bg.blue }

life cycle management

automatically applies a theme upon registration if that theme is already set as current

File.with_tmpfile (file) ->
  the_theme = theme_copy!
  file.contents = serpent.dump the_theme
  theme.register 'reloadme', file
  config.theme = 'reloadme'

  theme.unregister 'reloadme'
  the_theme.window.background = '#112233'
  file.contents = serpent.dump the_theme
  theme.register 'reloadme', file
  assert.equal '#112233', theme.current.window.background

keeps the loaded in-memory theme when the current is unregistered

File.with_tmpfile (file) ->
  file.contents = serpent.dump spec_theme
  theme.register 'keepme', file
  config.theme = 'keepme'
  theme.unregister 'keepme'
  assert.equal 'keepme', theme.current.name
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ui/window_spec.html b/site/source/versions/0.3/doc/spec/ui/window_spec.html deleted file mode 100644 index 7ac6e8459..000000000 --- a/site/source/versions/0.3/doc/spec/ui/window_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ui.Window - - - - - - - -
-
- -
- -

howl.ui.Window

local win

before_each ->
  win = Window!
  win\realize!

add_view(view [, placement, anchor])

adds the specified view

win\add_view Gtk.Label!
assert.equals 1, #win.views

returns a table containing x, y, width, height and the view

label = Gtk.Label!
assert.same { x: 1, y: 1, width: 1, height: 1, view: label }, win\add_view label

adds the new view to the right of the currently focused one by default

entry = Gtk.Entry!
win\add_view entry
entry\grab_focus!
label = Gtk.Label!
assert.same { x: 2, y: 1, width: 1, height: 1, view: label }, win\add_view label

(when placement is specified)

local view, entry

before_each ->
  entry = Gtk.Entry!
  win\add_view entry
  entry\grab_focus!
  view = Gtk.Entry!

"right_of" places the view on the right side of the focused child

assert.same { x: 2, y: 1, width: 1, height: 1, :view }, win\add_view view, 'right_of'

"left_of" places the view on the left side of the focused child

assert.same { x: 1, y: 1, width: 1, height: 1, :view }, win\add_view view, 'left_of'

"above" places the view above the focused child

assert.same { x: 1, y: 1, width: 1, height: 1, :view }, win\add_view view, 'above'
assert.same { x: 1, y: 2, width: 1, height: 1, view: entry }, win\get_view entry

"below" places the view below the focused child

assert.same { x: 1, y: 2, width: 1, height: 1, :view }, win\add_view view, 'below'

allows specifying the relative view to use with placement

win\add_view view, 'below'
next_view = Gtk.Label!
assert.same { x: 1, y: 2, width: 1, height: 1, view: next_view }, win\add_view next_view, 'left_of', view

creates new columns as needed

win\add_view view, 'right_of'
next_view = Gtk.Label!
assert.same { x: 2, y: 1, width: 1, height: 1, view: next_view }, win\add_view next_view, 'left_of', view
assert.same { 1, 2, 3 }, [v.x for v in *win.views]

remove_view(view)

removes the specified view

label = Gtk.Label!
win\add_view label
win\remove_view label
assert.equals 0, #win.views

removes the currently focused child if view is nil

entry = Gtk.Entry!
win\add_view entry
entry\grab_focus!
win\remove_view!
assert.equals 0, #win.views

raises an error if view is nil and no child has focus

label = Gtk.Label!
win\add_view label
assert.raises 'remove', -> win\remove_view!

set focus on its later sibling is possible

left = Gtk.Entry!
middle = Gtk.Entry!
right = Gtk.Entry!
win\add_view left
win\add_view middle
win\add_view right
middle\grab_focus!
win\remove_view middle
assert.is_true right.is_focus

set focus on its earlier sibling if no later sibling exists

left = Gtk.Entry!
middle = Gtk.Entry!
right = Gtk.Entry!
win\add_view left
win\add_view middle
win\add_view right
right\grab_focus!
win\remove_view right
assert.is_true middle.is_focus

.views

is a table of view tables, containing x, y and the view itself

label = Gtk.Label!
win\add_view label
assert.same { { x: 1, y: 1, width: 1, height: 1, view: label } }, win.views

ordered ascendingly

entry = Gtk.Entry!
win\add_view entry
entry\grab_focus!

l1 = Gtk.Label!
l2 = Gtk.Label!
win\add_view l1, 'left_of'
win\add_view l2, 'below'
assert.same { l1, entry, l2 }, [v.view for v in *win.views]

.current_view

is nil if no child is currently focused

assert.is_nil, win.current_view

is a table containing x, y and the view for the currently focused view

e1 = Gtk.Entry!
e2 = Gtk.Entry!
win\add_view e1
win\add_view e2

e1\grab_focus!
assert.same { x: 1, y: 1, width: 1, height: 1, view: e1 }, win.current_view

e2\grab_focus!
assert.same { x: 2, y: 1, width: 1, height: 1, view: e2 }, win.current_view

siblings(view, wraparound)

returns an empty table if there are no siblings

v1 = Gtk.Entry!
  win\add_view v1
  assert.same {}, win\siblings v1

defaults to the currently focused child if view is not provided

v1 = Gtk.Entry!
  win\add_view v1
  v1\grab_focus!
  assert.same {}, win\siblings!

returns an empty table if view is not provided and no child is focused

assert.same {}, win\siblings!

(when wraparound is false)

returns a table of siblings for the specified view when present

left = Gtk.Entry!
 right = Gtk.Entry!
 bottom = Gtk.Entry!
 win\add_view left
 win\add_view right, 'right_of', left
 win\add_view bottom, 'below', left

 assert.same { right: right, down: bottom }, win\siblings left, false
 assert.same { left: left, down: bottom }, win\siblings right, false
 assert.same { up: left }, win\siblings bottom, false

(when wraparound is true)

returns a table of siblings for the specified view in a wraparound fashion

left = Gtk.Entry!
  right = Gtk.Entry!
  bottom = Gtk.Entry!
  win\add_view left
  win\add_view right, 'right_of', left
  win\add_view bottom, 'below', left

  assert.same { left: bottom, right: right, up: bottom, down: bottom }, win\siblings left, true
  assert.same { left: left, right: bottom, up: bottom, down: bottom }, win\siblings right, true
  assert.same { left: right, right: left, up: left, down: left }, win\siblings bottom, true

column reflowing

local left, right, bottom

before_each ->
  left = Gtk.Entry!
  right = Gtk.Entry!
  bottom = Gtk.Entry!
  win\add_view left
  win\add_view right
  right\grab_focus!
  win\add_view bottom, 'below'

single columns as expanded as necessary

assert.same { x: 1, y: 2, width: 2, height: 1, view: bottom }, win\get_view bottom

columns to the right are adjusted after a remove of a left column

win\remove_view left
assert.same { x: 1, y: 1, width: 1, height: 1, view: right }, win\get_view right

columns to the left are adjusted after a remove of a right column

win\remove_view right
assert.same { x: 1, y: 1, width: 1, height: 1, view: left }, win\get_view left

rows are adjusted after removal of a middle column

middle = Gtk.Entry!
win\add_view middle, 'right_of', left
win\remove_view middle
assert.same { x: 1, y: 1, width: 1, height: 1, view: left }, win\get_view left
assert.same { x: 2, y: 1, width: 1, height: 1, view: right }, win\get_view right
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/ustring_spec.html b/site/source/versions/0.3/doc/spec/ustring_spec.html deleted file mode 100644 index d5003ce72..000000000 --- a/site/source/versions/0.3/doc/spec/ustring_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.ustrings - - - - - - - -
-
- -
- -

howl.ustrings

.ulen holds the number characters in the string

assert.equal 3, ('foo').ulen
assert.equal 3, ('åäö').ulen

.ulower is a lower cased version of the string

assert.equal 'abcåäö', ('aBCåÄÖ').ulower

.uupper is a upper cased version of the string

assert.equal 'ABCÅÄÖ', ('abcåäö').uupper

.ureverse is a reversed version of the string

assert.equal 'abcåäö', ('öäåcba').ureverse

.multibyte is true if the string contains multibyte characters

assert.is_false ('foo').multibyte
assert.is_true ('åäö').multibyte

.is_empty is true for the empty string

assert.is_true ('').is_empty
assert.is_false (' ').is_empty

.is_blank is true for a string that is empty or only contains whitespace

assert.is_true ('\t\r\n').is_blank
assert.is_false ('x').is_blank

.stripped contains the string without leading or trailing whitespace

assert.equal 'foo', ('  \tfoo').stripped
assert.equal 'foo', ('foo ').stripped
assert.equal 'foo', ('  \tfoo ').stripped
assert.equal '', ('  \t').stripped
assert.equal '', ('').stripped

ucompare(s1, s2) returns negative, 0 or positive if s1 is smaller, equal or greater than s2

assert.is_true 'a'\ucompare('b') < 0
assert.is_true 'a'\ucompare('ä') < 0
assert.equal 0, 'a'\ucompare('a')
assert.is_true 'ö'\ucompare('ä') > 0

is_valid_utf8(s) return true for valid utf8 strings only

assert.is_true ('abc\194\128').is_valid_utf8
assert.is_true ('\127').is_valid_utf8
assert.is_false ('\128').is_valid_utf8
assert.is_false ('abc\194').is_valid_utf8

starts_with(s) returns true if the string starts with the specified string

assert.is_true 'foobar'\starts_with 'foo'
assert.is_true 'foobar'\starts_with 'foobar'
assert.is_false 'foobar'\starts_with 'foobarx'
assert.is_false 'foobar'\starts_with '.oo'

ends_with(s) returns true if the string ends with the specified string

assert.is_true 'foobar'\ends_with 'bar'
assert.is_true 'foobar'\ends_with 'foobar'
assert.is_false 'foobar'\ends_with 'barx'
assert.is_false 'foobar'\ends_with '.ar'

contains(s) returns true if the string contains the specified string

assert.is_true 'foobar'\contains 'foobar'
assert.is_true 'foobar'\contains 'bar'
assert.is_true 'foobar'\contains 'foo'
assert.is_true 'foobar'\contains 'oba'
assert.is_false 'foobar'\contains 'arx'
assert.is_false 'foobar'\contains 'xfo'
assert.is_false 'foobar'\contains '.'

usub(i, [j])

s = 'aåäöx'

operates on characters instead of bytes

assert.equal 'aåä', s\usub 1, 3
assert.equal 'aåä', s\usub(1, 3)

adjusts the indexes similarily to string.sub

assert.equal 'äöx', s\usub 3 -- j defaults to -1
assert.equal 'öx', s\usub -2 -- i counts from back
assert.equal 'aåäöx', s\usub -7 -- is corrected to 1
assert.equal 'aåäöx', s\usub 1, 123 -- j is corrected to last character
assert.equal '', s\usub 3, 2 -- empty string when i < j

character access using indexing notation

single character strings can be accessed using indexing notation

s = 'aåäöx'
assert.equal 'a', s[1]
assert.equal 'ä', s[3]

accesses using invalid indexes returns an empty string

s = 'abc'
assert.equal '', s[0]
assert.equal '', s[4]

the index can be negative similarily to sub()

s = 'aåäöx'
assert.equal 'ä', s[-3]

umatch(pattern [, init])

init specifies a character offset

assert.same { 'ö', 4 }, { 'äåö'\umatch '(%S+)()', 3 }

if init is greater than the length nil is returned

assert.is_nil '1'\umatch '1', 2

accepts regex patterns

assert.same {'ö'}, { '/ö'\umatch r'\\p{L}'}

ugmatch(pattern)

returns character offsets instead of byte offsets

s = 'föo bãr'
gen = s\ugmatch '(%S+)()'
rets = {}
while true
  vals = { gen! }
  break if #vals == 0
  append rets, vals

assert.same { { 'föo', 4 }, { 'bãr', 8 } }, rets

accepts regex patterns

s = 'well hello there'
matches = [m for m in s\ugmatch r'\\w+']
assert.same { 'well', 'hello', 'there' }, matches

ufind(pattern [, init [, plain]])

returns character offsets instead of byte offsets

assert.same { 2, 4, 5 }, { 'ä öx'\ufind '%s.+x()' }

adjust middle-of-sequence position returns to character start

assert.same { 1, 1 }, { 'äöx'\ufind '%S' }

init specifies a character offset

assert.same { 3, 3, 'ö' }, { 'äåö'\ufind '(%S+)', 3 }

if init is greater than the length nil is returned

assert.is_nil '1'\ufind '1', 2

accepts regexes

assert.same { 2, 2 }, { '!ä öx'\ufind r'\\pL' }

returns empty match at init for empty string

assert.same { 2, 1 }, { 'abc'\ufind '', 2 }

rfind(pattern [, init])

searches backward from end using byte offsets

assert.same { 5, 6 }, { 'äöxx'\rfind 'xx' }

searches backward from init, when provided

assert.same { 5, 5 }, { 'äöxxx'\rfind 'x', 5 }

urfind(text [, init])

searches backwards from end using char offsets

assert.same { 4, 5 }, { 'äöxäöx'\urfind 'äö' }
assert.same { 3, 6 }, { 'äöxböx'\urfind 'xböx' }
assert.same { 1, 3 }, { 'äöxböx'\urfind 'äöx' }

returns nothing for no matches

assert.same {}, { 'hello'\urfind 'x' }

searches backwards from init, when provided

assert.same { 1, 2 }, { 'äöxäöx'\urfind 'äö', 4 }
assert.same { 1, 2 }, { 'äöxäöx'\urfind 'äö', -3 }
assert.same { 4, 5 }, { 'äöxäöx'\urfind 'äö', 5 }
assert.same { 4, 5 }, { 'äöxäöx'\urfind 'äö', -2 }

matches text entirely before init

assert.same {1, 2}, { 'abcabc'\urfind 'ab', 4 }

returns empty match before init for empty string

assert.same { 2, 1 }, { 'abc'\urfind '', 2 }

count(s, pattern = false)

returns the number of occurences of s within the string

assert.equal 1, 'foobar'\count 'foo'
assert.equal 2, 'foobar'\count 'o'
assert.equal 0, 'foobar'\count 'x'

s is evaluated as a pattern if <pattern> is true

assert.equal 3, 'foo'\count('%w', true)
assert.equal 2, 'foobar'\count(r'[ab]', true)

s is evaluated as a pattern if it is a regex, regardless of <pattern>

assert.equal 2, 'foobar'\count(r'[ab]')

byte_offset(...)

returns byte offsets for all character offsets passed as parameters

assert.same {1, 3, 5, 7}, { 'äåö'\byte_offset 1, 2, 3, 4 }

accepts non-increasing offsets

assert.same {1, 1}, { 'ab'\byte_offset 1, 1 }

raises an error for decreasing offsets

assert.raises 'Decreasing offset', -> 'äåö'\byte_offset 2, 1

raises error for out-of-bounds offsets

assert.raises 'out of bounds', -> 'äåö'\byte_offset 5
assert.raises 'offset', -> 'äåö'\byte_offset 0
assert.raises 'offset', -> 'a'\byte_offset -1

when parameters is a table, it returns a table for all offsets within that table

assert.same {1, 3, 5}, 'äåö'\byte_offset { 1, 2, 3 }

char_offset(...)

returns character offsets for all byte offsets passed as parameters

assert.same {1, 2, 3, 4}, { 'äåö'\char_offset 1, 3, 5, 7 }

accepts non-increasing offsets

assert.same {2, 2}, { 'ab'\char_offset 2, 2 }

raises an error for decreasing offsets

assert.raises 'Decreasing offset', -> 'äåö'\char_offset 3, 1

raises error for out-of-bounds offsets

assert.raises 'out of bounds', -> 'ab'\char_offset 4
assert.raises 'offset', -> 'äåö'\char_offset 0
assert.raises 'offset', -> 'a'\char_offset -1

when parameters is a table, it returns a table for all offsets within that table

assert.same {1, 2, 3, 4}, 'äåö'\char_offset { 1, 3, 5, 7 }
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/util/matcher_spec.html b/site/source/versions/0.3/doc/spec/util/matcher_spec.html deleted file mode 100644 index 54c083832..000000000 --- a/site/source/versions/0.3/doc/spec/util/matcher_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.util.Matcher - - - - - - - -
-
- -
- -

howl.util.Matcher

matches if the search matches exactly

c = { 'One', 'Green Fields', 'two' }
m = Matcher c
assert.same { 'One' }, m('ne')

candidates are automatically converted to strings

candidate = setmetatable {}, __tostring: -> 'auto'
m = Matcher { candidate }
assert.same { candidate }, m('auto')

candidates can be multi-valued tables

c = { { 'One', 'Uno' } }
m = Matcher c
assert.same { c[1] }, m('One')

multi-valued candidates are automatically converted to strings

candidate = setmetatable {}, __tostring: -> 'auto'
m = Matcher { { candidate, 'desc' } }
assert.same { { candidate, 'desc' } }, m('auto')

prefers boundary matches over exact ones

c = { 'kiss her', 'some/stuff/here', 'openssh', 'sss hhh' }
m = Matcher c
assert.same {
  'sss hhh',
  'some/stuff/here'
  'openssh',
}, m('ssh')

prefers early occurring matches over ones at the end

c = { 'Discard all apples', 'all aardvarks' }
m = Matcher c
assert.same {
  'all aardvarks',
  'Discard all apples'
}, m('aa')

prefers shorter matching candidates over longer ones

c = { 'x/tools.sh', 'x/torx' }
m = Matcher c
assert.same {
  'x/torx',
  'x/tools.sh'
}, m('to')

prefers tighter matches to longer ones

c = { 'awesome_apples', 'an_aardvark'  }

m = Matcher c
assert.same {
  'an_aardvark',
  'awesome_apples',
}, m('aa')

"special" characters are matched as is

c = { 'Item 2. 1%w', 'Item 22 2a' }
m = Matcher c
assert.same { 'Item 2. 1%w' }, m('%w')
assert.same { }, m('.*')

boundary matches can not skip separators

m = Matcher { 'nih/says/knights' }
assert.same { 'nih/says/knights' }, m('sk')
assert.same {}, m('nk')

boundary matches are as tight as possible

assert.same { how: 'boundary', 1, 6, 7 }, Matcher.explain 'hth', 'hail the howl'

(boundary matches)

matches if the search matches at boundaries

m = Matcher { 'green fields', 'green sfinx' }
assert.same { 'green fields' }, m('gf')
assert.same { 'apaass_so' }, Matcher({'apaass_so'})('as')

matches if the search matches at upper case boundaries

m = Matcher { 'camelCase', 'a CreditCard', 'chacha' }
assert.same { 'camelCase', 'a CreditCard' }, m('cc')

allows for multiple-character boundaries

m = Matcher { 'green/_fields', 'green sfinx' }
assert.same { 'green/_fields' }, m('gf')

explain(search, text)

sets .how to the type of match

assert.equal 'exact', Matcher.explain('fu', 'snafu').how

returns a list of character offsets indicating where <search> matched

assert.same { how: 'exact', 4, 5, 6 }, Matcher.explain 'ƒlu', 'sñaƒlux'
assert.same { how: 'boundary', 1, 4, 9, 10 }, Matcher.explain 'itʂo', 'iʂ that ʂo'

lower-cases the search and text just as for matching

assert.not_nil Matcher.explain 'FU', 'ʂnafu'
assert.not_nil Matcher.explain 'fu', 'SNAFU'

accepts ustring both for <search> and <text>

assert.not_nil Matcher.explain 'FU', 'snafu'

with reverse matching (reverse = true specified as an option)

prefers late occurring exact matches over ones at the start

c = { 'xmatch me', 'me xmatch' }
m = Matcher c, reverse: true
assert.same {
  'me xmatch'
  'xmatch me',
}, m('mat')

prefers late occurring boundary matches over ones at the start

c = { 'match natchos', 'me match now' }
m = Matcher c, reverse: true
assert.same {
  'me match now'
  'match natchos',
}, m('mn')

still prefers tighter matches to longer ones

c = { 'an_aardvark', 'a_apple' }

m = Matcher c, reverse: true
assert.same {
  'a_apple',
  'an_aardvark',
}, m('aa')

still prefers boundary matches over straight ones

c = { 'some/stuff/here', 'sshopen', 'open/ssh', 'ss xh' }
m = Matcher c, reverse: true

assert.same {
  'open/ssh',
  'sshopen',
  'some/stuff/here'
}, m('ssh')

explain(search, text) works correctly

assert.same { how: 'exact', 7, 8, 9 }, Matcher.explain 'aƒl', 'ƒluxsñaƒlux', reverse: true
assert.same { how: 'boundary', 1, 5 }, Matcher.explain 'as', 'app_spec.fu', reverse: true

handles boundary matches

handles boundary matches

m = Matcher { 'spec/aplication_spec.moon' }, reverse: true
assert.same { 'spec/aplication_spec.moon' }, m('as')

allows for multiple-character boundaries

m = Matcher { 'spec/aplication/_spec.moon' }, reverse: true
assert.same { 'spec/aplication/_spec.moon' }, m('as')

with preserve_order = true specified as an option

preserves order of matches, irrespective of match score

c = {'xabx0', 'ax_bx1', 'xabx2', 'ax_bx3'}
m = Matcher c, preserve_order: true
assert.same c, m('ab')
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/util/paths_spec.html b/site/source/versions/0.3/doc/spec/util/paths_spec.html deleted file mode 100644 index 7e610b632..000000000 --- a/site/source/versions/0.3/doc/spec/util/paths_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.util.paths - - - - - - - -
-
- -
- -

howl.util.paths

local tmpdir

before_each ->
  tmpdir = File.tmpdir!
  File.mkdir tmpdir / 'subdir'

after_each ->
  tmpdir\rm_r!

get_dir_and_leftover

returns the home dir for empty input

assert.same {File.home_dir, ''}, {paths.get_dir_and_leftover ''}

returns the root directory for "/"

assert.same {File.home_dir.root_dir, ''}, {paths.get_dir_and_leftover '/'}

returns the matched path and unmatched parts of a path

assert.same {tmpdir, 'unmatched'}, {paths.get_dir_and_leftover tostring(tmpdir / 'unmatched')}

when given a directory path ending in "/", matches the given directory

assert.same {tmpdir / 'subdir', ''}, {paths.get_dir_and_leftover tostring(tmpdir)..'/subdir/'}

when given a directory path not ending in "/", matches the parent directory

assert.same {tmpdir, 'subdir'}, {paths.get_dir_and_leftover tostring(tmpdir)..'/subdir'}

unmatched part can contain slashes

assert.same {tmpdir, 'unmatched/no/such/file'}, {paths.get_dir_and_leftover tostring(tmpdir / 'unmatched/no/such/file')}

(is given a non absolute path)

uses the home dir as the base path

assert.same {File.home_dir, 'unmatched-asdf98y23903943masgb sdf'}, {paths.get_dir_and_leftover 'unmatched-asdf98y23903943masgb sdf'}
- -
- - - diff --git a/site/source/versions/0.3/doc/spec/vc_spec.html b/site/source/versions/0.3/doc/spec/vc_spec.html deleted file mode 100644 index 986577781..000000000 --- a/site/source/versions/0.3/doc/spec/vc_spec.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Howl :: Spec - howl.VC - - - - - - - -
-
- -
- -

howl.VC

after_each -> VC.unregister name for name in pairs VC.available

.available is a table of all available vcs

handler = find: -> nil
VC.register 'foo', handler
assert.same VC.available, foo: handler

.register(name, handler)

raises an error if name is missing

assert.raises 'name', -> VC.register nil

raises an error if handler is missing or incomplete

assert.raises 'handler', -> VC.register 'foo', nil
assert.raises '.find', -> VC.register 'foo', {}

.unregister(name)

raises an error if name is missing

assert.raises 'name', -> VC.unregister nil

removes the specified vc from .available

VC.register 'foo', find: -> nil
VC.unregister 'foo'
assert.is_nil VC.available.foo

for_file(file)

returns the first non-nil find()-result from handlers

vc = root: '', name: 'vc', files: -> {}
VC.register 'foo', find: -> nil
VC.register 'no', find: -> nil
VC.register 'yes', find: -> vc
assert.equal VC.for_file('file'), vc

returns nil if no handler returns non-nil

assert.is_nil VC.for_file('file')
VC.register 'foo', find: -> nil
assert.is_nil VC.for_file('file')

(validating the loaded vc)

vc = nil
before_each ->
  vc = {}
  VC.register 'vc', find: -> vc

raises an error if vc.files() is missing

assert.raises 'files', -> VC.for_file 'file'

raises an error if vc.root is missing

vc.files = -> {}
assert.raises 'root', -> VC.for_file 'file'

raises an error if vc.name is missing

vc.files = -> {}
vc.name = 'vc'
assert.raises 'root', -> VC.for_file 'file'
- -
- - - diff --git a/site/source/versions/0.5/doc/api/application.html b/site/source/versions/0.5/doc/api/application.html new file mode 100644 index 000000000..f63ae6702 --- /dev/null +++ b/site/source/versions/0.5/doc/api/application.html @@ -0,0 +1,124 @@ + + + + + + + + + Howl :: howl.Application + + + + + + + + + + + +
+

howl.Application

+

The Application object acts as the main hub within the Howl editor. There exists one and only one instantiated application object per Howl instance, available as howl.app.

Properties

buffers

A list of currently open Buffer:s. The list is ordered by how recently a buffer was shown. Thus, a currently showing buffer will come before a buffer that is not shown, and not showing buffers will be ordered according to the timestamp they were last shown.

editor

Points to the currently active Editor, if any.

editors

A list of all existing Editor:s. Each editor can be placed in only one window at a time, but this list holds all editors present for the current Howl instance - regardless of whether they’re placed in the currently focused window or not.

idle

A number providing information on how long the application has been idle, in seconds (with fractions). As the idle is reset upon activity this is useful primarily in timers and idle callbacks.

next_buffer

This is the most recent buffer that is currently not showing in any editor. If all buffers are currently showing it’s the first buffer in .buffers.

window

Points to the currently focused Window.

windows

A list of existing Window:s.

Methods

add_buffer (buffer, show = true)

Adds the existing buffer to .buffers. If show is true, shows the buffer in the currently active editor.

close_buffer (buffer, force = false)

Removes buffer from .buffers. If the buffer is modified, and force is not true, the user is prompted before closing the buffer.

editor_for_buffer (buffer)

Returns the editor currently showing buffer, or nil if the buffer is not currently showing in any editor.

new_buffer (buffer_mode = nil)

Creates a new buffer, and adds it to .buffers. buffer_mode can optionally be specified to assign a specific mode for the new buffer directly. When not specified, the default mode is used. See mode for more information about buffer modes.

new_editor (options = {})

Creates a new Editor. Unless options specify otherwise, the newly created editor is added to the currently focused window, to the right of the currently focused existing editor. It’s set to show the buffer from the .next_buffer property. The editor is added to .editors before the return of the method.

options can contain any of the following keys:

Example use (Moonscript):

buffer = howl.app\new_buffer!
buffer.text = 'Show this text in the new buffer'
howl.app\new_editor :buffer

new_window (properties = {})

Creates a new application Window. properties is table of window properties to set for the new window, such as title, height and width. The window is added to .windows before the return of the method. Returns the newly created window.

open_file (file, editor = _G.editor)

Opens the provided file. By default, unless editor specifies a specific editor to open the file into, the file is opened in the currently active editor. Emits the file-opened signal if the file was opened successfully. If the file was successfully opened, returns the Buffer and the Editor holding the buffer. Otherwise nil is returned.

save_all ()

Saves all modified buffers in one go.

save_session ()

Saves the current editing session to disk. This includes things such as information about what buffers are open, the current state of the window, etc.

synchronize ()

Synchronizes all open files with their respective files, if any. This will cause any non-modified buffers to be reloaded from disk, should the file be more recently modified than the buffer.

quit (force = false)

Requests for Howl to quit. If any open buffers are modified, and force is not true, the user will be prompted for verification before actually quitting.

+
+ + + + + diff --git a/site/source/versions/0.3/doc/api/bindings.html b/site/source/versions/0.5/doc/api/bindings.html similarity index 88% rename from site/source/versions/0.3/doc/api/bindings.html rename to site/source/versions/0.5/doc/api/bindings.html index d395813a2..d42804c43 100644 --- a/site/source/versions/0.3/doc/api/bindings.html +++ b/site/source/versions/0.5/doc/api/bindings.html @@ -10,56 +10,54 @@ + + + -
-
- -
- -

howl.bindings

+ +
+

howl.bindings

howl.bindings
-

Overview

howl.bindings handles the set of active key bindings within Howl. “Bindings” in this context refers to the various actions that will be executed as the result of a key press, and we say that a key is bound to a certain action whenever that action will trigger as a result of the key being pressed.

The way this works in Howl is that bindings keeps track of an arbitrary number of “keymaps” that are searched whenever a key is pressed. A keymap is simple a Lua table with keys matching key translations. The keymaps are stacked, and they will all be searched for a matching action whenever a key is pressed. Typically processing stops whenever the first action has been triggered, but it’s possible for a handler to allow a key press to propagate further down the stack if it so chooses.

Keymaps are as said simple Lua tables, that maps “key translations” to actions. Each key press is represented as a “key event”, which is also a simple Lua table. Below you can see an example of a key event resulting from pressing Control + Shift + a:

-- Key event
{
  character = "A", -- the character corresponding to the key press, if any
  key_code = 65,   -- the code of the key pressed
  key_name = "a",  -- a symbolic name for the key pressed, if any
  alt = false,     -- true if the alt key was held down during the key press
  control = true,  -- true if the control key was held down during the key press
  meta = false,    -- true if the meta key was held down during the key press
  shift = true,    -- true if the shift key was held down during the key press
  super = false    -- true if the super key was held down during the key press
}

As part of processing the key event is translated to a list of possible string representations using translate_key, which for the above example would result in the following list of translations:

{
  "ctrl_A",
  "ctrl_shift_a",
  "ctrl_shift_65"
}

All keymaps are then searched in order for keys matching any of the translations. If you read the documentation for process you’ll see that all key events are processed for a particular originating source. In the typical case this will be “editor”, indicating the key press originated from an editor. When searching keymaps, any keymap is first inspected to see if it has a source specific keymap table, in which case this is searched first before any top-level bindings. Consider the following keymap:

{
  ctrl_b = function() print("A general binding") end,
  ctrl_c = 'my_general_command',

  editor = {
    ctrl_shift_a = function(editor) print("An editor binding") end
  }
}

Should the key event example above be dispatched against this keymap with the source being “editor”, it would trigger the “ctrl_shift_a” binding. The top-level bindings (e.g. “ctrl_b”) would trigger regardless of source. Also note that the editor specific binding can make use of a source specific extra argument, an editor instance in this case.

Any matching value found in a keymap is considered an action. Should a keymap not have any matching keys but have a callable field named on_unhandled, that is invoked with the key event, event source, key translations and any extra parameters passed to dispatch, and any truthy result is used as the action. See the documentation for dispatch for further information about these parameters.

Actions can be one of three different things:

  • It can be a string, in which case it’s considered a command and will be dispatched using command.run.

  • It can be a callable object (a function or table providing a meta-table __call), in which case it’s invoked with any extra parameters passed to dispatch (the typical case being the editor instance for which the key press originated). The key event will be considered handled unless the handler returns false.

  • It can be an ordinary, non-callable, table. This table is interpreted as an additional keymap, which will be pushed using the pop option.

Indirect bindings

When writing keymaps for non-editor sources, a special key called binding_for can be used in the keymap to bind an action to a key press indirectly by using a command name as the key. For example, if you wanted to support pasting in your readline input, instead of binding the action directly to the ctrl_v key press, you might want to bind whichever key is bound to the editor-paste command. This can be specified by the following keymap:

{
  binding_for = {
    ['editor-paste']: function() ... end
  }
}

This ensures that if the user binds a new key press to the editor-paste command, that new key press will now trigger the bound action above, providing a better experience for the user.

Protip:

You can use the describe-key command to interactively view information for any particular key press, i.e. the key event and translations.

See also:

  • The spec for howl.bindings

Properties

.is_capturing

True if there’s currently a capture handler installed, and false otherwise.

.keymaps

This is a list of the currently active keymaps. This is a stack, with latter keymaps taking precedence over earlier ones.

Functions

action_for (translation)

Searches the stack of kemaps for the given key translation and returns the first bound action found. An action may be a string (i.e. a command name) or a function object. If no binding can be found for the translation, nil is returned.

cancel_capture ()

Removes any installed capture handler.

capture (handler)

Installs a capture handler. The handler, which should be callable, will intercept any key events being sent to process for processing. It will be invoked with the key event, source, key translations and any extra parameters passed to process. Unless the handler returns false, it will automatically be removed after the invocation. There can be only one capture handler installed at any given time. Installing a capture handler when an existing one is already set will simply override the previous one.

dispatch (key_event, source, keymaps, …)

Explicitly dispatches the key event against the specified list of keymaps. source is the source of the key press, e.g. “editor”. keymaps is the list of keymaps that will be searched. Any additional arguments are passed as is to any callable actions.

Note:

Unlike process, dispatch will not automatically include any of the keymaps in the binding stack, it will only search keymaps.

keystrokes_for (action, source)

Finds all keystrokes (i.e. translations) that are bound to the specified action. source, if given, specifies the source specific keymaps to search as well. Returns a table containing all keystrokes found, or an empty table if no binding was found.

For example:

-- look up the binding for the `project-open` command:
howl.bindings.keystrokes_for('project-open')
-- => { 'ctrl_p' }

-- look up the binding for the `buffer-search-forward` command:
howl.bindings.keystrokes_for('buffer-search-forward', 'editor')
-- => { 'ctrl_f' }

-- but since that's a command only bound for editor sources,
-- it's not bound globally
howl.bindings.keystrokes_for('buffer-search-forward')
-- => {}

pop ()

Pops the top-most keymap of the stack. Raises an error if the stack is empty.

process (key_event, source, extra_keymaps = {}, …)

Processes the key_event by dispatching it against the list of keymaps present in the bindings stack. source is the source of the key press, e.g. “editor”. extra_keymaps is an optional list of additional keymaps that will be searched; if specified these will be searched in order before any of the keymaps in the stack. Any additional arguments are passed as is to any callable actions.

Should any capture handler be installed via capture, this will be invoked first and further processing will be skipped.

The key-press signal is emitted before dispatching, and further processing will be skipped if this is handled.

push (keymap, options = {})

Pushes keymap onto the bindings stack. options can contain any of the following keys:

  • block: When set to true, this prevents any keymaps lower in the stack to be searched for matching actions, effectively making this the only keymap available.

  • pop: When set to true, this causes the keymap to be popped from the stack automatically after the next key dispatch. If pop is set, the map is implicitly blocking as well.

remove (keymap)

Removes the specified keymap from the stack. Returns true if the keymap was removed successfully and false if it was not found.

translate_key (event)

Returns a list of translations for the passed in key event.

Example (Lua):

-- Given the following key event
local key_event = {
  alt = false,
  character = "A",
  control = true,
  key_code = 65,
  key_name = "a",
  meta = false,
  shift = true,
  super = false
}

bindings.translate_key(key_event)
-- returns
{
  "ctrl_A",
  "ctrl_shift_a",
  "ctrl_shift_65"
}
-