/wrapping-editor

Generic Haskell text-editor logic and Brick widget for use with fixed-width fonts.

Primary LanguageHaskellApache License 2.0Apache-2.0

Tests WEditor WEditorHyphen WEditorBrick
Haskell CI Hackage Status Hackage Status Hackage Status

This library contains a simple text-editing component for fixed-size fonts. It can either be used as a Brick Widget or as the basis for creating a new widget for another UI library.

Here are some important features:

  • Supports dynamic viewport resizing while preserving the view position.
  • Customizable line-wrapping policies, to include special line rendering.
  • Includes a line-wrapping policy for word hyphenation.
  • Efficient editing operations for long documents.

If you have any problems using this library, or if you would like to see new features, please see the issues page. Also check out the library reference.

Installation

This library is split into 3 separate packages:

  • WEditor is the base package, containing the generic editor logic and simple line-wrapping and hyphenation policies. This can be used as the basis for a new editor widget for a UI library without depending on any of the other packages.

  • WEditorHyphen contains language-specific hyphenation policies for use with both WEditor and WEditorBrick. This package is not required in order to use either of those packages. You will need to explicitly install the hyphenation package in order to have access to the rules for each supported language.

  • WEditorBrick contains just the Brick Widget.

All of these packages can be installed from Hackage using cabal. You will need to explicitly install WEditor in order to set up editors in either of the other packages.

  • Just the base package:

    # cabal < 2.0
    cabal install WEditor
    
    # cabal >= 2.0
    cabal install --lib WEditor
  • Everything:

    # cabal < 2.0
    cabal install WEditor WEditorBrick hyphenation WEditorHyphen
    
    # cabal >= 2.0
    cabal install --lib WEditor WEditorBrick hyphenation WEditorHyphen

Using with Brick

See brick-example.hs for an example program that uses WrappingEditor.

You can run the example with:

ghc -threaded example/WEditorBrick/brick-example.hs
example/WEditorBrick/brick-example README.md

Press Esc to exit when you are finished. The final contents of the editor will be sent to stdout without modifying the file.

You can customize the widget using the following helper functions from the WrappingEditor module:

  • doEditor allows you to use FixedFontEditor and FixedFontViewer functions to extract info from the editor, e.g., for custom rendering.

  • genericEditor allows you to use any custom editor component that instantiates both FixedFontEditor and FixedFontViewer, e.g., an editor for a custom character type. (You might also need custom rendering and event handling.)

  • mapEditor allows you to transform the editor with FixedFontEditor and FixedFontViewer functions, e.g., calling editing actions in a custom event handler. (If you use a custom event handler, call updateEditorExtent before applying editor actions.)

Using the Generic Editor

import WEditor.LineWrap
import WEditor.Document

-- 1. Split your doc into paragraphs. Paragraph splitting is handled by the
--    caller so that the library can avoid end-of-line considerations.
paragraphs = map UnparsedPara $ lines $ "Your document contents."

-- 2. Create an editor. This example uses lazy word hyphenation.
editor = editDocument (breakWords lazyHyphen) paragraphs

-- 3a. Edit the document using actions from `Viewer` and `Editor`. Don't forget
--     to set the viewport size! If either dimension is < 1, the text will be
--     unbounded in that direction.
editor' = foldl (flip ($)) editor [
    viewerResizeAction (80,24),
    editorEndAction,
    editorEnterAction,
    editorAppendAction "Here is a new paragraph.",
    -- Resizing while editing is fine.
    viewerResizeAction (7,3),
    -- Use this if you don't like scrolling below the last line.
    viewerFillAction
  ]

-- 3b. Get the viewport contents for display. This does not necessarily fill up
--     the entire view area; pad it if necessary.
display = getVisible editor'

-- 4. Extract the edited contents.
final = unlines $ map upText $ exportData editor'

Wrapping Policies

  • breakExact works for all character types because it breaks lines at exactly the editor width.

  • breakWords p takes a WordSplitter policy p to split words. Character support depends on the existence of a WordSplitter for the character type. In theory, it supports all character types.

    • noHyphen avoids splitting words if at all possible, and it never uses hyphens. This policy requires a WordChar instance for the character type.

    • lazyHyphen splits words without any dictionary awareness, and attempts to keep at least 2 characters of the word on each line. This policy requires WordChar and HyphenChar instances for the character type.

    • langHyphen l (from the optional WEditorHyphen packge) uses language-specific hyphenation rules, e.g., English_US.

    • Create custom word-splitting by creating a new WordSplitter. This can be used for supporting new character types, adding dictionary awareness, expanding the characters that are considered to be a part of words, etc.

  • Create a completely new wrapping policy with an instance of FixedFontParser. This can be used for things that breakWords cannot support, e.g., line indentation and tab expansion.

Character Support

The generic editor can support any character type for which a wrapping policy is available. The editor for Brick currently only supports Char, but it will likely be modified to support other character types.