howl.ui.theme

assigning directly to .current raises an error

File.with_tmpfile (file) ->
  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 = "cantload!"
  theme.register 'error', file
  config.theme = 'error'
  assert.match log.last_error.message, 'Theme error'

assigns the loaded theme to .current and sets .name

File.with_tmpfile (file) ->
  theme.register 'foo', file
  config.theme = 'foo'
  assert.equal 'foo', theme.current.name

(switching themes)

drops the styles, flairs and gutter color set only by the previous theme

File.with_tmpfile (first) ->
  File.with_tmpfile (second) ->
    first.contents = '
      style.first-only { color: red; }
      flair.first-only { shape: pipe; border-color: red; }
      .gutter { color: red; }
    '
    theme.register 'first', first
    theme.register 'second', second
    config.theme = 'first'
    assert.is_not_nil style.first_only
    assert.is_not_nil flair.get 'first_only'
    assert.equal 'red', aullar_config.gutter_color

    config.theme = 'second'
    assert.is_nil style.first_only
    assert.is_nil flair.get 'first_only'
    assert.equal aullar_config.definition_for('gutter_color').default, aullar_config.gutter_color

(style parsing)

load_css = (css) ->
  File.with_tmpfile (file) ->
    file.contents = css
    theme.register 'styles', file
    config.theme = 'styles'
    theme.current.styles

parses style declarations into the internal style format

assert.equal 'red', load_css('style.foo { color: red; }').foo.color

underscores the style names

assert.equal 'red', load_css('style.foo-bar { color: red; }').foo_bar.color

accepts style names given with underscores

assert.equal 'red', load_css('style.foo_bar { color: red; }').foo_bar.color

accepts a last declaration without a semicolon

assert.equal 'red', load_css('style.foo { color: red }').foo.color

disregards comments from the declarations

assert.equal 'red', load_css([[
  style.foo {
    /* what: never mind me */
    color: red;
  }
]]).foo.color

translates font-style: italic as font.italic

assert.same {
  foo: {
    font: italic: true
  }
}, load_css 'style.foo { font-style: italic; }'

translates font-weight: bold as font.bold

assert.same {
  foo: {
    font: bold: true
  }
}, load_css 'style.foo { font-weight: bold; }'

translates font-size font.size

assert.same {
  foo: {
    font: size: 'large'
  }
}, load_css 'style.foo { font-size: large; }'

translates numeric font sizes, in points, as numbers

assert.equal 14, load_css('style.foo { font-size: 14; }').foo.font.size
assert.equal 14, load_css('style.foo { font-size: 14pt; }').foo.font.size

translates font-family as font.family

assert.same {
  foo: {
    font: family: 'monospace'
  }
}, load_css 'style.foo { font-family: monospace; }'

strips quotes and spaces from a list of font families

assert.equal 'Latin Modern,Purisa', load_css(
  [[style.foo { font-family: "Latin Modern", 'Purisa'; }]]
).foo.font.family

translates color as color

assert.equals 'green', load_css('style.foo { color: green; }').foo.color

converts colors that Pango does not understand to hex

assert.equals '#ff0000', load_css('style.foo { color: rgb(255, 0, 0); }').foo.color

translates background-color as background

assert.equals 'green', load_css(
  'style.foo { background-color: green; }'
).foo.background

allows alpha values for background-color

assert.equals '#dddddd70', load_css(
  'style.foo { background-color: #dddddd70; }'
).foo.background

implements the Gtk alpha function

assert.equals '#00ff0080', load_css(
  'style.foo { background-color: alpha(#00ff00, 0.5); }'
).foo.background

handles color names for the Gtk alpha function

assert.equals '#00000080', load_css(
  'style.foo { background-color: alpha(black, 0.5); }'
).foo.background

translates text-decoration underline as underline

assert.is_true load_css(
  'style.foo { text-decoration: underline; }'
).foo.underline

translates text-decoration line-through as strike_through

assert.is_true load_css(
  'style.foo { text-decoration: line-through; }'
).foo.strike_through

(flair parsing)

load_css = (css) ->
  File.with_tmpfile (file) ->
    file.contents = css
    theme.register 'flairs', file
    config.theme = 'flairs'
    theme.current.flairs

parses flairs declarations into the internal flairs format

assert.same {
  type: 'pipe',
  foreground: 'red'
}, load_css('flair.foo { shape: pipe; border-color: red; }').foo

underscores the style names

assert.equal 'red', load_css(
  'flair.foo-bar { shape: pipe; border-color: red; }'
).foo_bar.foreground

translates width as line_width

assert.equal 2, load_css(
  'flair.foo { shape: pipe; border-color: red; width: 2; }'
).foo.line_width

translates border-style as line_type

assert.equal 'dotted', load_css(
  'flair.foo { shape: pipe; border-color: red; border-style: dotted; }'
).foo.line_type

translates minimum-width as min_width

assert.equal 2, load_css(
  'flair.foo { shape: pipe; minimum-width: 2; }'
).foo.min_width

translates color as text_color

assert.equal 'red', load_css(
  'flair.foo { shape: pipe; color: red; }'
).foo.text_color

translates background-color as background

assert.equal 'red', load_css(
  'flair.foo { shape: pipe; background-color: red; }'
).foo.background

translates border-radius as corner_radius

assert.equal 2, load_css(
  'flair.foo { shape: rounded-rectangle; border-radius: 2; }'
).foo.corner_radius

translates height as a number, or "text"

assert.equal 10, load_css('flair.foo { shape: pipe; height: 10px; }').foo.height
assert.equal 'text', load_css('flair.foo { shape: pipe; height: text; }').foo.height

(mistakes)

load_css = (css) ->
  File.with_tmpfile (file) ->
    file.contents = css
    theme.register 'mistakes', file
    config.theme = 'mistakes'
    theme.current

assert_logged = (pattern) ->
  assert.includes log.last_error.message, pattern

logs unknown properties and applies the rest of the rule

t = load_css 'style.foo { colour: red; color: blue; }'
assert_logged "Theme error: style.foo: unknown property 'colour'"
assert.equal 'blue', t.styles.foo.color

logs declarations that are not "property: value"

load_css 'style.foo { font: bold: true }'
assert_logged "style.foo: invalid declaration 'font: bold: true'"

logs invalid values

load_css 'style.foo { font-weight: 700; }'
assert_logged "style.foo: invalid font-weight '700'"

load_css 'flair.foo { shape: pipe; border-color: nocolor; }'
assert_logged "flair.foo: invalid color 'nocolor'"

logs transparent text colors, which Pango can not draw

load_css 'style.foo { color: alpha(red, 0.5); }'
assert_logged "style.foo: text colors can't be transparent"

logs and drops flairs without a valid shape

t = load_css 'flair.foo { border-color: red; } flair.bar { shape: blob; }'
assert.is_nil t.flairs.foo
assert.is_nil t.flairs.bar
assert_logged 'no valid shape, ignoring it'

logs style and flair selectors with more than a single name

load_css 'window { color: red; } style.foo, style.bar { color: red; }'
assert_logged "unsupported selector 'style.foo, style.bar'"

(custom value extractions)

load_css = (css) ->
  File.with_tmpfile (file) ->
    file.contents = css
    theme.register 'custom', file
    config.theme = 'custom'
    theme.current.custom

extracts .gutter color as gutter_color

assert.equal 'red', load_css(
  '.gutter { color: red; }'
).gutter_color

(CSS var declarations)

load_css = (css) ->
  File.with_tmpfile (file) ->
    file.contents = css
    theme.register 'vars', file
    config.theme = 'vars'
    theme.current

parses vars from :root and allows use of them

assert.equal 'green', load_css(
  '
  :root {
  }
  style.foo { color: var(--my-var); }
  '
).styles.foo.color

allows vars to reference other vars

assert.equal 'green', load_css(
  '
  :root {
  }
  style.foo { color: var(--sub); }
  '
).styles.foo.color