From 5085437ac82e8136e12fb600764c00fb8dc19450 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 3 Aug 2020 00:37:06 -0400 Subject: [PATCH 01/31] Add the cursor keys to the macros help dialog --- ui/messages.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/messages.go b/ui/messages.go index daa29c6..bd6dfb3 100644 --- a/ui/messages.go +++ b/ui/messages.go @@ -156,6 +156,7 @@ represent themselves. Compound keys can be: - , +, , , , , From 8a46b8978973031d7adab858dc7592ce2caad096 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Sat, 15 Aug 2020 12:44:32 -0400 Subject: [PATCH 02/31] See if travis supports building with go 1.15... --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5ae1f94..3653a8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ go: - 1.12.x - 1.13.x - 1.14.x +- 1.15.x notifications: email: true before_install: @@ -33,7 +34,7 @@ deploy: script: bash scripts/do-release.sh on: all_branches: true - condition: "$TRAVIS_OS_NAME = linux && $TRAVIS_GO_VERSION = 1.14.x" + condition: "$TRAVIS_OS_NAME = linux && $TRAVIS_GO_VERSION = 1.15.x" - provider: gcs skip_cleanup: true access_key_id: GOOGKKO2OYB5BE3XDNBJMDRK @@ -46,4 +47,4 @@ deploy: on: repo: gcla/termshark all_branches: true - condition: "$TRAVIS_OS_NAME = linux && $TRAVIS_GO_VERSION = 1.14.x" + condition: "$TRAVIS_OS_NAME = linux && $TRAVIS_GO_VERSION = 1.15.x" From bf985811d0388c8050ca023f01822f8375e24e9d Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Thu, 3 Sep 2020 23:25:07 -0400 Subject: [PATCH 03/31] Prevent cursor keys from moving between packet views I did this for the default layout, but didn't do it for the alternative layouts e.g. with a wide packet list view, then side-by-side structure and hex views beneath. This addresses issue #94. --- ui/ui.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/ui.go b/ui/ui.go index 1e75dfc..8f2145e 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -3297,7 +3297,7 @@ func Build() (*gowid.App, error) { altview1Pile = resizable.NewPile([]gowid.IContainerWidget{ &gowid.ContainerWidget{ - IWidget: packetListViewHolder, + IWidget: packetListViewWithKeys, D: weight(1), }, &gowid.ContainerWidget{ @@ -3305,7 +3305,7 @@ func Build() (*gowid.App, error) { D: flow, }, &gowid.ContainerWidget{ - IWidget: packetStructureViewHolder, + IWidget: packetStructureViewWithKeys, D: weight(1), }, }) @@ -3326,7 +3326,7 @@ func Build() (*gowid.App, error) { D: units(1), }, &gowid.ContainerWidget{ - IWidget: packetHexViewHolder, + IWidget: packetHexViewHolderWithKeys, D: weight(1), }, }) @@ -3358,7 +3358,7 @@ func Build() (*gowid.App, error) { assignTo(&altview2Cols, resizable.NewColumns([]gowid.IContainerWidget{ &gowid.ContainerWidget{ - IWidget: packetStructureViewHolder, + IWidget: packetStructureViewWithKeys, D: weight(1), }, &gowid.ContainerWidget{ @@ -3366,7 +3366,7 @@ func Build() (*gowid.App, error) { D: units(1), }, &gowid.ContainerWidget{ - IWidget: packetHexViewHolder, + IWidget: packetHexViewHolderWithKeys, D: weight(1), }, }), @@ -3382,7 +3382,7 @@ func Build() (*gowid.App, error) { assignTo(&altview2Pile, resizable.NewPile([]gowid.IContainerWidget{ &gowid.ContainerWidget{ - IWidget: packetListViewHolder, + IWidget: packetListViewWithKeys, D: weight(1), }, &gowid.ContainerWidget{ From 00d0658692fd28e183410bdc8ed00fc4cf1bf209 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Fri, 4 Sep 2020 22:29:08 -0400 Subject: [PATCH 04/31] Move modeswap underneath a new theme directory I'll add some extra functions in here to handle reading a color from the config file. --- {modeswap => theme/modeswap}/modeswap.go | 0 ui/palette.go | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {modeswap => theme/modeswap}/modeswap.go (100%) diff --git a/modeswap/modeswap.go b/theme/modeswap/modeswap.go similarity index 100% rename from modeswap/modeswap.go rename to theme/modeswap/modeswap.go diff --git a/ui/palette.go b/ui/palette.go index 3c454f9..d247be2 100644 --- a/ui/palette.go +++ b/ui/palette.go @@ -7,7 +7,7 @@ package ui import ( "github.com/gcla/gowid" - "github.com/gcla/termshark/v2/modeswap" + "github.com/gcla/termshark/v2/theme/modeswap" ) //====================================================================== From 962362d46183af1878596c6116ddf515a7b749bc Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 00:49:29 -0400 Subject: [PATCH 05/31] A function to construct a color by looking it up in the config file MakeColorSafe() is a gowid function to construct a color based on a string. Gowid walks through various interpretations of the string to try to construct the most likely color. This function adds an interpretation at the beginning, which is as a key in the termshark toml file. This lets you add something like [theme] flashy = "theme.red" red = "#ff0000" Now you can programmatically use "theme.flashy" as an argument and construct a color from it. This function takes an an extra argument, meaning foreground or background. This allows for pairs of colors to be used: [theme] ocean = ["#ffffff", "#0000ff"] // white on blue If you call MakeColorSafe() with "theme.ocean" and theme.Background, you'll get the blue color. Values can refer to other keys in the toml file; this can be done up to 10 times before the function gives up (in case there's a loop). --- theme/utils.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 theme/utils.go diff --git a/theme/utils.go b/theme/utils.go new file mode 100644 index 0000000..53dd9d2 --- /dev/null +++ b/theme/utils.go @@ -0,0 +1,62 @@ +// Copyright 2019-2020 Graham Clark. All rights reserved. Use of this source +// code is governed by the MIT license that can be found in the LICENSE +// file. + +// package theme provides utilities for customizing the styling of termshark. +package theme + +import ( + "github.com/gcla/gowid" + "github.com/gcla/termshark/v2" + log "github.com/sirupsen/logrus" +) + +//====================================================================== + +type Layer int + +const ( + Foreground Layer = 0 + Background Layer = iota +) + +// MakeColorSafe extends gowid's MakeColorSafe function, prefering to interpret +// its string argument as a toml file config key lookup first; if this fails, then +// fall back to gowid.MakeColorSafe, which will then read colors as urwid color names, +// #-prefixed hex digits, grayscales, etc. +func MakeColorSafe(s string, l Layer) (gowid.Color, error) { + loops := 10 + cur := s + for { + next := termshark.ConfString(cur, "") + if next != "" { + cur = next + } else { + next := termshark.ConfStringSlice(cur, []string{}) + if len(next) != 2 { + break + } else { + cur = next[l] + } + } + loops -= 1 + if loops == 0 { + break + } + } + col, err := gowid.MakeColorSafe(cur) + if err == nil { + return gowid.Color{IColor: col, Id: s}, nil + } + col, err = gowid.MakeColorSafe(s) + if err != nil { + log.Infof("Could not understand configured theme color '%s'", s) + } + return col, err +} + +//====================================================================== +// Local Variables: +// mode: Go +// fill-column: 110 +// End: From 386bd11989a89f415b50b78ed31cd699c407e023 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 01:00:19 -0400 Subject: [PATCH 06/31] Update to latest gowid For terminal color setting bug fixes. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 814c583..00eb0ab 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/fsnotify/fsnotify v1.4.7 github.com/gcla/deep v1.0.2 - github.com/gcla/gowid v1.1.1-0.20200801025312-b6db1a298dd9 + github.com/gcla/gowid v1.1.1-0.20200907041935-63bc5705d2f5 github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359 github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29 github.com/go-test/deep v1.0.2 // indirect diff --git a/go.sum b/go.sum index 8689d43..9d36e6c 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/gcla/gowid v1.1.1-0.20200727022650-fcf8767b2efb h1:8k8GFeUivRxdRfuN8y github.com/gcla/gowid v1.1.1-0.20200727022650-fcf8767b2efb/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= github.com/gcla/gowid v1.1.1-0.20200801025312-b6db1a298dd9 h1:5vki4zDKlsADgPf8ZLMKW42TkA30MklzvmpmeY/pNpI= github.com/gcla/gowid v1.1.1-0.20200801025312-b6db1a298dd9/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= +github.com/gcla/gowid v1.1.1-0.20200907041935-63bc5705d2f5 h1:IWHYS/s3P3/RdFzelGZLhSd4NZcetUHP3dRMD7X5igo= +github.com/gcla/gowid v1.1.1-0.20200907041935-63bc5705d2f5/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359 h1:3xEhacR7pIJV8daurdBygptxhzTJeYFqJp1V6SDl+pE= github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359/go.mod h1:Wn+pZpM98JHSOYkPDtmdvlqmc0OzQGHWOsHB2d28WtQ= github.com/gcla/tcell v1.1.2-0.20200115035344-b90e69b9dbe0 h1:6fMu73gAbCSAYAGFhMU1G9h5IilNI9wlvEdV4KMF93U= From 50f7642575c322dcc9cc22b49197a391cc2c2c1a Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 01:00:41 -0400 Subject: [PATCH 07/31] Make sure termshark's screen color range is correctly set A gowid bug meant that this range was never initialized because I delayed activation of the terminal. --- cmd/termshark/termshark.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/termshark/termshark.go b/cmd/termshark/termshark.go index afeb056..b188562 100644 --- a/cmd/termshark/termshark.go +++ b/cmd/termshark/termshark.go @@ -1018,6 +1018,9 @@ Loop: return 1 } + // This needs to run after the toml config file is loaded. + ui.SetupColors() + // Start tcell/gowid events for keys, etc appRunner.Start() From e0737277d0ff77612f19d3cb5d13ada01a4d1212 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 01:02:02 -0400 Subject: [PATCH 08/31] A first attempt to make termshark themeable This work is attempting to address issue #95. @kevinhwang91 suggested making termshark themeable so that colors could line up with other terminal apps that do support themes. All of termshark's palette keys are now exposed as theme settings. Below is an example of using them from my termshark.toml file. This is my attempt to use the dracula theme's colors (https://draculatheme.com/) instead of the defaults. It works best in dark mode, I think. The simple rules are: - add dark-mode rules under [themes.rules.dark] - add light mode rules under [themes.rules.light] - define colors as = . The value can refer to another key, or use a syntax like #rrggbb (you can use other gowid color names too). - define theme rules as = [fgcolor, bgcolor] In the docs I'll explain what all these theme names mean. [themes] [themes.dracula] bg = "#282a36" bg2 = "#373844" bg3 = "#464752" bg4 = "#565761" black = "themes.dracula.bg" comment = "#6272a4" current = "#44475a" cyan = "#8be9fd" fg = "#f8f8f2" fg2 = "#e2e2dc" fg3 = "#ccccc7" fg4 = "#b6b6b2" green = "#50fa7b" orange = "#ffb86c" other-blue = "#0189cc" pink = "#ff79c6" purple = "#bd93f9" red = "#ff5555" white = "themes.dracula.fg" yellow = "#f1fa8c" [themes.rules] [themes.rules.dark] button = ["themes.dracula.white","themes.dracula.black"] button-focus = ["themes.dracula.black","themes.dracula.purple"] button-selected = ["themes.dracula.white","themes.dracula.black"] cmdline = ["themes.dracula.black","themes.dracula.yellow"] cmdline-button = ["themes.dracula.yellow","themes.dracula.black"] copy-mode = ["themes.dracula.black","themes.dracula.yellow"] copy-mode-alt = ["themes.dracula.yellow","themes.dracula.black"] copy-mode-label = ["themes.dracula.white","themes.dracula.red"] current-capture = ["themes.dracula.white","themes.unused"] default = ["themes.dracula.white","themes.dracula.black"] dialog = ["themes.dracula.black","themes.dracula.yellow"] dialog-button = ["themes.dracula.yellow","themes.dracula.black"] filter-intermediate = ["themes.dracula.black","themes.dracula.orange"] filter-invalid = ["themes.dracula.black","themes.dracula.red"] filter-menu = ["themes.dracula.white","themes.dracula.black"] filter-valid = ["themes.dracula.black","themes.dracula.green"] hex-byte-selected = ["themes.dracula.black","themes.dracula.purple"] hex-byte-unselected = ["themes.dracula.black","themes.dracula.black"] hex-field-selected = ["themes.dracula.black","themes.dracula.cyan"] hex-field-unselected = ["themes.dracula.black","themes.dracula.white"] hex-interval-selected = ["themes.dracula.white","themes.dracula.bg4"] hex-interval-unselected = ["themes.dracula.white","themes.dracula.bg3"] hex-layer-selected = ["themes.dracula.white","themes.dracula.bg4"] hex-layer-unselected = ["themes.dracula.white","themes.dracula.bg3"] packet-list-cell-focus = ["themes.dracula.black","themes.dracula.purple"] packet-list-cell-selected = ["themes.dracula.white","themes.dracula.bg4"] packet-list-row-focus = ["themes.dracula.bg4","themes.dracula.cyan"] packet-list-row-selected = ["themes.dracula.white","themes.dracula.bg3"] packet-struct-focus = ["themes.dracula.bg4","themes.dracula.cyan"] packet-struct-selected = ["themes.dracula.black","themes.dracula.bg4"] progress = ["themes.dracula.white","themes.dracula.black"] progress-complete = ["themes.unused","themes.dracula.purple"] spinner = ["themes.dracula.yellow","themes.dracula.black"] stream-client = ["themes.dracula.black","themes.dracula.red"] stream-match = ["themes.dracula.black","themes.dracula.yellow"] stream-search = ["themes.dracula.black","themes.dracula.white"] stream-server = ["themes.dracula.cyan","themes.dracula.comment"] title = ["themes.dracula.red","themes.unused"] [themes.rules.light] button = ["themes.dracula.black","themes.dracula.white"] button-focus = ["themes.dracula.black","themes.dracula.purple"] button-selected = ["themes.dracula.black","themes.dracula.fg4"] cmdline = ["themes.dracula.black","themes.dracula.yellow"] cmdline-button = ["themes.dracula.yellow","themes.dracula.black"] copy-mode = ["themes.dracula.white","themes.dracula.yellow"] copy-mode-alt = ["themes.dracula.yellow","themes.dracula.white"] copy-mode-label = ["themes.dracula.black","themes.dracula.red"] current-capture = ["themes.dracula.black","themes.unused"] default = ["themes.dracula.black","themes.dracula.white"] dialog = ["themes.dracula.black","themes.dracula.yellow"] dialog-button = ["themes.dracula.yellow","themes.dracula.black"] filter-intermediate = ["themes.dracula.black","themes.dracula.orange"] filter-invalid = ["themes.dracula.black","themes.dracula.red"] filter-menu = ["themes.dracula.black","themes.dracula.white"] filter-valid = ["themes.dracula.black","themes.dracula.green"] hex-byte-selected = ["themes.dracula.black","themes.dracula.purple"] hex-byte-unselected = ["themes.dracula.black","themes.dracula.white"] hex-field-selected = ["themes.dracula.black","themes.dracula.cyan"] hex-field-unselected = ["themes.dracula.black","themes.dracula.bg4"] hex-interval-selected = ["themes.dracula.white","themes.dracula.bg4"] hex-interval-unselected = ["themes.dracula.black","themes.dracula.fg3"] hex-layer-selected = ["themes.dracula.white","themes.dracula.bg4"] hex-layer-unselected = ["themes.dracula.black","themes.dracula.fg3"] packet-list-cell-focus = ["themes.dracula.black","themes.dracula.purple"] packet-list-cell-selected = ["themes.dracula.black","themes.dracula.fg4"] packet-list-row-focus = ["themes.dracula.black","themes.dracula.cyan"] packet-list-row-selected = ["themes.dracula.black","themes.dracula.fg1"] packet-struct-focus = ["themes.dracula.black","themes.dracula.cyan"] packet-struct-selected = ["themes.dracula.black","themes.dracula.fg3"] progress = ["themes.dracula.black","themes.dracula.white"] progress-complete = ["themes.unused","themes.dracula.purple"] spinner = ["themes.dracula.yellow","themes.dracula.white"] stream-client = ["themes.dracula.white","themes.dracula.red"] stream-match = ["themes.dracula.white","themes.dracula.yellow"] stream-search = ["themes.dracula.white","themes.dracula.black"] stream-server = ["themes.dracula.cyan","themes.dracula.comment"] title = ["themes.dracula.red","themes.unused"] --- pdmltree/pdmltree.go | 8 +- ui/convsui.go | 12 +- ui/palette.go | 402 ++++++++++++++++++++----------- ui/ui.go | 26 +- widgets/filter/filter.go | 4 +- widgets/minibuffer/minibuffer.go | 8 +- 6 files changed, 290 insertions(+), 170 deletions(-) diff --git a/pdmltree/pdmltree.go b/pdmltree/pdmltree.go index 096bc61..9c31776 100644 --- a/pdmltree/pdmltree.go +++ b/pdmltree/pdmltree.go @@ -90,16 +90,16 @@ func (n *Model) HexLayers(pos int, includeFirst bool) []hexdumper2.LayerStyler { res = append(res, hexdumper2.LayerStyler{ Start: c.Pos, End: c.Pos + c.Size, - ColUnselected: "hex-bottom-unselected", - ColSelected: "hex-bottom-selected", + ColUnselected: "hex-layer-unselected", + ColSelected: "hex-layer-selected", }) for _, c2 := range c.Children_ { if c2.Pos <= pos && pos < c2.Pos+c2.Size { res = append(res, hexdumper2.LayerStyler{ Start: c2.Pos, End: c2.Pos + c2.Size, - ColUnselected: "hex-top-unselected", - ColSelected: "hex-top-selected", + ColUnselected: "hex-field-unselected", + ColSelected: "hex-field-selected", }) } } diff --git a/ui/convsui.go b/ui/convsui.go index 95b2a13..100eaed 100644 --- a/ui/convsui.go +++ b/ui/convsui.go @@ -708,10 +708,10 @@ func (w *ConvsUiWidget) OnData(data string, app gowid.IApp) { TableSeparator: divider.NewUnicode(), VerticalSeparator: nil, CellStyleProvided: true, - CellStyleSelected: gowid.MakePaletteRef("pkt-list-cell-selected"), - CellStyleFocus: gowid.MakePaletteRef("pkt-list-cell-focus"), + CellStyleSelected: gowid.MakePaletteRef("packet-list-cell-selected"), + CellStyleFocus: gowid.MakePaletteRef("packet-list-cell-focus"), HeaderStyleProvided: true, - HeaderStyleFocus: gowid.MakePaletteRef("pkt-list-cell-focus"), + HeaderStyleFocus: gowid.MakePaletteRef("packet-list-cell-focus"), }, Layout: table.LayoutOptions{ Widths: wids, @@ -720,7 +720,7 @@ func (w *ConvsUiWidget) OnData(data string, app gowid.IApp) { ptblModel := psmlmodel.New( tblModel, - gowid.MakePaletteRef("pkt-list-row-focus"), + gowid.MakePaletteRef("packet-list-row-focus"), ) if currentShortName, ok := convs.OfficialNameToType[cur]; ok { @@ -736,8 +736,8 @@ func (w *ConvsUiWidget) OnData(data string, app gowid.IApp) { boundedTbl := NewRowFocusTableWidget( tbl, - "pkt-list-row-selected", - "pkt-list-row-focus", + "packet-list-row-selected", + "packet-list-row-focus", ) var _ list.IWalker = boundedTbl diff --git a/ui/palette.go b/ui/palette.go index d247be2..bc7ef73 100644 --- a/ui/palette.go +++ b/ui/palette.go @@ -6,166 +6,286 @@ package ui import ( + "fmt" + "github.com/gcla/gowid" + "github.com/gcla/termshark/v2/theme" "github.com/gcla/termshark/v2/theme/modeswap" ) //====================================================================== var ( - LightGray gowid.GrayColor = gowid.MakeGrayColor("g74") - MediumGray gowid.GrayColor = gowid.MakeGrayColor("g50") - DarkGray gowid.GrayColor = gowid.MakeGrayColor("g35") - BrightBlue gowid.RGBColor = gowid.MakeRGBColor("#08f") - BrightGreen gowid.RGBColor = gowid.MakeRGBColor("#6f2") - LightRed gowid.RGBColor = gowid.MakeRGBColor("#ebb") - LightBlue gowid.RGBColor = gowid.MakeRGBColor("#abf") - DarkRed gowid.RGBColor = gowid.MakeRGBColor("#311") - DarkBlue gowid.RGBColor = gowid.MakeRGBColor("#01f") + LightGray gowid.IColor + MediumGray gowid.IColor + DarkGray gowid.IColor + BrightBlue gowid.IColor + BrightGreen gowid.IColor + LightRed gowid.IColor + LightBlue gowid.IColor + DarkRed gowid.IColor + DarkBlue gowid.IColor //====================================================================== // Regular mode // - // 256 color < 256 color - PktListRowSelectedBgReg *modeswap.Color = modeswap.New(MediumGray, gowid.ColorBlack) - PktListRowFocusBgReg *modeswap.Color = modeswap.New(BrightBlue, gowid.ColorBlue) - PktListCellSelectedFgReg *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorWhite) - PktListCellSelectedBgReg *modeswap.Color = modeswap.New(DarkGray, gowid.ColorBlack) - PktStructSelectedBgReg *modeswap.Color = modeswap.New(MediumGray, gowid.ColorBlack) - PktStructFocusBgReg *modeswap.Color = modeswap.New(BrightBlue, gowid.ColorBlue) - HexTopUnselectedFgReg *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorWhite) - HexTopUnselectedBgReg *modeswap.Color = modeswap.New(MediumGray, gowid.ColorBlack) - HexTopSelectedFgReg *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorWhite) - HexTopSelectedBgReg *modeswap.Color = modeswap.New(BrightBlue, gowid.ColorBlue) - HexBottomUnselectedFgReg *modeswap.Color = modeswap.New(gowid.ColorBlack, gowid.ColorWhite) - HexBottomUnselectedBgReg *modeswap.Color = modeswap.New(LightGray, gowid.ColorBlack) - HexBottomSelectedFgReg *modeswap.Color = modeswap.New(gowid.ColorBlack, gowid.ColorWhite) - HexBottomSelectedBgReg *modeswap.Color = modeswap.New(LightGray, gowid.ColorBlack) - HexCurUnselectedFgReg *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorBlack) - HexCurUnselectedBgReg *modeswap.Color = modeswap.New(gowid.ColorBlack, gowid.ColorWhite) - HexLineFgReg *modeswap.Color = modeswap.New(gowid.ColorBlack, gowid.ColorWhite) - HexLineBgReg *modeswap.Color = modeswap.New(LightGray, gowid.ColorBlack) - FilterValidBgReg *modeswap.Color = modeswap.New(BrightGreen, gowid.ColorGreen) - StreamClientFg *modeswap.Color = modeswap.New(DarkRed, gowid.ColorWhite) - StreamClientBg *modeswap.Color = modeswap.New(LightRed, gowid.ColorDarkRed) - StreamServerFg *modeswap.Color = modeswap.New(DarkBlue, gowid.ColorWhite) - StreamServerBg *modeswap.Color = modeswap.New(LightBlue, gowid.ColorBlue) - - RegularPalette gowid.Palette = gowid.Palette{ - "default": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorWhite), - "title": gowid.MakeForeground(gowid.ColorDarkRed), - "pkt-list-row-focus": gowid.MakePaletteEntry(gowid.ColorWhite, PktListRowFocusBgReg), - "pkt-list-cell-focus": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorPurple), - "pkt-list-row-selected": gowid.MakePaletteEntry(gowid.ColorWhite, PktListRowSelectedBgReg), - "pkt-list-cell-selected": gowid.MakePaletteEntry(PktListCellSelectedFgReg, PktListCellSelectedBgReg), - "pkt-struct-focus": gowid.MakePaletteEntry(gowid.ColorWhite, PktStructFocusBgReg), - "pkt-struct-selected": gowid.MakePaletteEntry(gowid.ColorWhite, PktStructSelectedBgReg), - "filter-menu-focus": gowid.MakeStyledPaletteEntry(gowid.ColorBlack, gowid.ColorWhite, gowid.StyleBold), - "filter-valid": gowid.MakePaletteEntry(gowid.ColorBlack, FilterValidBgReg), - "filter-invalid": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed), - "filter-intermediate": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorOrange), - "dialog": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorYellow), - "dialog-buttons": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorBlack), - "minibuffer": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorYellow), - "minibuffer-buttons": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorBlack), - "button": gowid.MakePaletteEntry(gowid.ColorDarkBlue, ButtonBg), - "button-focus": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorDarkBlue), - "button-selected": gowid.MakePaletteEntry(PktListCellSelectedFgReg, PktListCellSelectedBgReg), - "progress-default": gowid.MakeStyledPaletteEntry(gowid.ColorWhite, gowid.ColorBlack, gowid.StyleBold), - "progress-complete": gowid.MakeStyleMod(gowid.MakePaletteRef("progress-default"), gowid.MakeBackground(gowid.ColorMagenta)), - "progress-spinner": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorBlack), - "hex-cur-selected": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorMagenta), - "hex-cur-unselected": gowid.MakePaletteEntry(HexCurUnselectedFgReg, HexCurUnselectedBgReg), - "hex-top-selected": gowid.MakePaletteEntry(HexTopSelectedFgReg, HexTopSelectedBgReg), - "hex-top-unselected": gowid.MakePaletteEntry(HexTopUnselectedFgReg, HexTopUnselectedBgReg), - "hex-bottom-selected": gowid.MakePaletteEntry(HexBottomSelectedFgReg, HexBottomSelectedBgReg), - "hex-bottom-unselected": gowid.MakePaletteEntry(HexBottomUnselectedFgReg, HexBottomUnselectedBgReg), - "hexln-selected": gowid.MakePaletteEntry(HexLineFgReg, HexLineBgReg), - "hexln-unselected": gowid.MakePaletteEntry(HexLineFgReg, HexLineBgReg), - "copy-mode-indicator": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorDarkRed), - "copy-mode": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorYellow), - "copy-mode-alt": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorBlack), - "stream-client": gowid.MakePaletteEntry(StreamClientFg, StreamClientBg), - "stream-server": gowid.MakePaletteEntry(StreamServerFg, StreamServerBg), - "stream-match": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorYellow), - "stream-search": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack), + ButtonSelectedFgReg *modeswap.Color + ButtonSelectedBgReg *modeswap.Color + PktListRowSelectedBgReg *modeswap.Color + PktListRowFocusBgReg *modeswap.Color + PktListCellSelectedFgReg *modeswap.Color + PktListCellSelectedBgReg *modeswap.Color + PktStructSelectedBgReg *modeswap.Color + PktStructFocusBgReg *modeswap.Color + HexFieldUnselectedFgReg *modeswap.Color + HexFieldUnselectedBgReg *modeswap.Color + HexFieldSelectedFgReg *modeswap.Color + HexFieldSelectedBgReg *modeswap.Color + HexLayerUnselectedFgReg *modeswap.Color + HexLayerUnselectedBgReg *modeswap.Color + HexLayerSelectedFgReg *modeswap.Color + HexLayerSelectedBgReg *modeswap.Color + HexByteUnselectedFgReg *modeswap.Color + HexByteUnselectedBgReg *modeswap.Color + HexIntervalSelectedFgReg *modeswap.Color + HexIntervalSelectedBgReg *modeswap.Color + HexIntervalUnselectedFgReg *modeswap.Color + HexIntervalUnselectedBgReg *modeswap.Color + FilterValidBgReg *modeswap.Color + StreamClientFg *modeswap.Color + StreamClientBg *modeswap.Color + StreamServerFg *modeswap.Color + StreamServerBg *modeswap.Color + + RegularPalette gowid.Palette + + //====================================================================== + // Dark mode + // + + ButtonSelectedFgDark *modeswap.Color + ButtonSelectedBgDark *modeswap.Color + ButtonBgReg *modeswap.Color + PktListRowSelectedFgDark *modeswap.Color + PktListRowSelectedBgDark *modeswap.Color + PktListRowFocusBgDark *modeswap.Color + PktListCellSelectedFgDark *modeswap.Color + PktListCellSelectedBgDark *modeswap.Color + PktStructSelectedFgDark *modeswap.Color + PktStructSelectedBgDark *modeswap.Color + PktStructFocusBgDark *modeswap.Color + HexFieldUnselectedFgDark *modeswap.Color + HexFieldUnselectedBgDark *modeswap.Color + HexFieldSelectedFgDark *modeswap.Color + HexFieldSelectedBgDark *modeswap.Color + HexLayerUnselectedFgDark *modeswap.Color + HexLayerUnselectedBgDark *modeswap.Color + HexLayerSelectedFgDark *modeswap.Color + HexLayerSelectedBgDark *modeswap.Color + HexByteUnselectedFgDark *modeswap.Color + HexByteUnselectedBgDark *modeswap.Color + HexIntervalSelectedBgDark *modeswap.Color + HexIntervalSelectedFgDark *modeswap.Color + HexIntervalUnselectedBgDark *modeswap.Color + HexIntervalUnselectedFgDark *modeswap.Color + FilterValidBgDark *modeswap.Color + ButtonBgDark *modeswap.Color + StreamClientFgDark *modeswap.Color + StreamClientBgDark *modeswap.Color + StreamServerFgDark *modeswap.Color + StreamServerBgDark *modeswap.Color + + DarkModePalette gowid.Palette +) + +func SetupColors() { + LightGray = gowid.MakeGrayColor("g74") + MediumGray = gowid.MakeGrayColor("g50") + DarkGray = gowid.MakeGrayColor("g35") + BrightBlue = gowid.MakeRGBColor("#08f") + BrightGreen = gowid.MakeRGBColor("#6f2") + LightRed = gowid.MakeRGBColor("#ebb") + LightBlue = gowid.MakeRGBColor("#abf") + DarkRed = gowid.MakeRGBColor("#311") + DarkBlue = gowid.MakeRGBColor("#01f") + + //====================================================================== + // Regular mode + // + + // 256 color < 256 color + ButtonBgReg = modeswap.New(lbg("button", LightGray), gowid.ColorWhite) + ButtonSelectedFgReg = modeswap.New(lfg("button-selected", gowid.ColorWhite), gowid.ColorWhite) + ButtonSelectedBgReg = modeswap.New(lbg("button-selected", DarkGray), gowid.ColorBlack) + PktListRowSelectedBgReg = modeswap.New(lbg("packet-list-row-selected", MediumGray), gowid.ColorBlack) + PktListRowFocusBgReg = modeswap.New(lbg("packet-list-row-focus", BrightBlue), gowid.ColorBlue) + PktListCellSelectedFgReg = modeswap.New(lfg("packet-list-cell-selected", gowid.ColorWhite), gowid.ColorWhite) + PktListCellSelectedBgReg = modeswap.New(lbg("packet-list-cell-selected", DarkGray), gowid.ColorBlack) + PktStructSelectedBgReg = modeswap.New(lbg("packet-struct-selected", MediumGray), gowid.ColorBlack) + PktStructFocusBgReg = modeswap.New(lbg("packet-struct-focus", BrightBlue), gowid.ColorBlue) + HexFieldUnselectedFgReg = modeswap.New(lfg("hex-field-unselected", gowid.ColorWhite), gowid.ColorWhite) + HexFieldUnselectedBgReg = modeswap.New(lbg("hex-field-unselected", MediumGray), gowid.ColorBlack) + HexFieldSelectedFgReg = modeswap.New(lfg("hex-field-selected", gowid.ColorWhite), gowid.ColorWhite) + HexFieldSelectedBgReg = modeswap.New(lbg("hex-field-selected", BrightBlue), gowid.ColorBlue) + HexLayerUnselectedFgReg = modeswap.New(lfg("hex-layer-unselected", gowid.ColorBlack), gowid.ColorWhite) + HexLayerUnselectedBgReg = modeswap.New(lbg("hex-layer-unselected", LightGray), gowid.ColorBlack) + HexLayerSelectedFgReg = modeswap.New(lfg("hex-layer-selected", gowid.ColorBlack), gowid.ColorWhite) + HexLayerSelectedBgReg = modeswap.New(lbg("hex-layer-selected", LightGray), gowid.ColorBlack) + HexByteUnselectedFgReg = modeswap.New(lfg("hex-byte-unselected", gowid.ColorWhite), gowid.ColorBlack) + HexByteUnselectedBgReg = modeswap.New(lbg("hex-byte-unselected", gowid.ColorBlack), gowid.ColorWhite) + HexIntervalSelectedFgReg = modeswap.New(lfg("hex-interval-selected", gowid.ColorBlack), gowid.ColorWhite) + HexIntervalSelectedBgReg = modeswap.New(lbg("hex-interval-selected", LightGray), gowid.ColorBlack) + HexIntervalUnselectedFgReg = modeswap.New(lfg("hex-interval-unselected", gowid.ColorBlack), gowid.ColorWhite) + HexIntervalUnselectedBgReg = modeswap.New(lbg("hex-interval-unselected", LightGray), gowid.ColorBlack) + FilterValidBgReg = modeswap.New(lbg("filter-valid", BrightGreen), gowid.ColorGreen) + StreamClientFg = modeswap.New(lfg("stream-client", DarkRed), gowid.ColorWhite) + StreamClientBg = modeswap.New(lbg("stream-client", LightRed), gowid.ColorDarkRed) + StreamServerFg = modeswap.New(lfg("stream-server", DarkBlue), gowid.ColorWhite) + StreamServerBg = modeswap.New(lbg("stream-server", LightBlue), gowid.ColorBlue) + + RegularPalette = gowid.Palette{ + "default": gowid.MakePaletteEntry(lfg("default", gowid.ColorBlack), lbg("default", gowid.ColorWhite)), + "title": gowid.MakeForeground(lfg("title", gowid.ColorDarkRed)), + "packet-list-row-focus": gowid.MakePaletteEntry(lfg("packet-list-row-focus", gowid.ColorWhite), lbg("packet-list-row-focus", PktListRowFocusBgReg)), + "packet-list-row-selected": gowid.MakePaletteEntry(lfg("packet-list-row-selected", gowid.ColorWhite), PktListRowSelectedBgReg), + "packet-list-cell-focus": gowid.MakePaletteEntry(lfg("packet-list-cell-focus", gowid.ColorWhite), lbg("packet-list-cell-focus", gowid.ColorPurple)), + "packet-list-cell-selected": gowid.MakePaletteEntry(PktListCellSelectedFgReg, PktListCellSelectedBgReg), + "packet-struct-focus": gowid.MakePaletteEntry(lfg("packet-struct-focus", gowid.ColorWhite), PktStructFocusBgReg), + "packet-struct-selected": gowid.MakePaletteEntry(lfg("packet-struct-selected", gowid.ColorWhite), PktStructSelectedBgReg), + "filter-menu": gowid.MakeStyledPaletteEntry(lfg("filter-menu", gowid.ColorBlack), lbg("filter-menu", gowid.ColorWhite), gowid.StyleBold), + "filter-valid": gowid.MakePaletteEntry(lfg("filter-valid", gowid.ColorBlack), FilterValidBgReg), + "filter-invalid": gowid.MakePaletteEntry(lfg("filter-invalid", gowid.ColorBlack), lbg("filter-invalid", gowid.ColorRed)), + "filter-intermediate": gowid.MakePaletteEntry(lfg("filter-intermediate", gowid.ColorBlack), lbg("filter-intermediate", gowid.ColorOrange)), + "dialog": gowid.MakePaletteEntry(lfg("dialog", gowid.ColorBlack), lbg("dialog", gowid.ColorYellow)), + "dialog-buttons": gowid.MakePaletteEntry(lfg("dialog-buttons", gowid.ColorYellow), lbg("dialog-buttons", gowid.ColorBlack)), + "cmdline": gowid.MakePaletteEntry(lfg("cmdline", gowid.ColorBlack), lbg("cmdline", gowid.ColorYellow)), + "cmdline-buttons": gowid.MakePaletteEntry(lfg("cmdline-buttons", gowid.ColorYellow), lbg("cmdline-buttons", gowid.ColorBlack)), + "button": gowid.MakePaletteEntry(lfg("button", gowid.ColorDarkBlue), ButtonBgReg), + "button-focus": gowid.MakePaletteEntry(lfg("button-focus", gowid.ColorWhite), lbg("button-focus", gowid.ColorDarkBlue)), + "button-selected": gowid.MakePaletteEntry(ButtonSelectedFgReg, ButtonSelectedBgReg), + "progress-default": gowid.MakeStyledPaletteEntry(lfg("progress-default", gowid.ColorWhite), lbg("progress-default", gowid.ColorBlack), gowid.StyleBold), + "progress-complete": gowid.MakeStyleMod(gowid.MakePaletteRef("progress-default"), gowid.MakeBackground(gowid.ColorMagenta)), + "progress-spinner": gowid.MakePaletteEntry(lfg("progress-spinner", gowid.ColorYellow), lbg("progress-spinner", gowid.ColorBlack)), + "hex-byte-selected": gowid.MakePaletteEntry(lfg("hex-byte-selected", gowid.ColorWhite), lbg("hex-byte-selected", gowid.ColorMagenta)), + "hex-byte-unselected": gowid.MakePaletteEntry(HexByteUnselectedFgReg, HexByteUnselectedBgReg), + "hex-field-selected": gowid.MakePaletteEntry(HexFieldSelectedFgReg, HexFieldSelectedBgReg), + "hex-field-unselected": gowid.MakePaletteEntry(HexFieldUnselectedFgReg, HexFieldUnselectedBgReg), + "hex-layer-selected": gowid.MakePaletteEntry(HexLayerSelectedFgReg, HexLayerSelectedBgReg), + "hex-layer-unselected": gowid.MakePaletteEntry(HexLayerUnselectedFgReg, HexLayerUnselectedBgReg), + "hex-interval-selected": gowid.MakePaletteEntry(HexIntervalSelectedFgReg, HexIntervalSelectedBgReg), + "hex-interval-unselected": gowid.MakePaletteEntry(HexIntervalUnselectedFgReg, HexIntervalUnselectedBgReg), + "copy-mode-label": gowid.MakePaletteEntry(lfg("copy-mode-label", gowid.ColorWhite), lbg("copy-mode-label", gowid.ColorDarkRed)), + "copy-mode": gowid.MakePaletteEntry(lfg("copy-mode", gowid.ColorBlack), lbg("copy-mode", gowid.ColorYellow)), + "copy-mode-alt": gowid.MakePaletteEntry(lfg("copy-mode-alt", gowid.ColorYellow), lbg("copy-mode-alt", gowid.ColorBlack)), + "stream-client": gowid.MakePaletteEntry(StreamClientFg, StreamClientBg), + "stream-server": gowid.MakePaletteEntry(StreamServerFg, StreamServerBg), + "stream-match": gowid.MakePaletteEntry(lfg("stream-match", gowid.ColorBlack), lbg("stream-match", gowid.ColorYellow)), + "stream-search": gowid.MakePaletteEntry(lfg("stream-search", gowid.ColorWhite), lbg("stream-search", gowid.ColorBlack)), } //====================================================================== // Dark mode // - // 256 color < 256 color - ButtonBg *modeswap.Color = modeswap.New(LightGray, gowid.ColorWhite) - PktListRowSelectedFgDark *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorBlack) - PktListRowSelectedBgDark *modeswap.Color = modeswap.New(DarkGray, gowid.ColorWhite) - PktListRowFocusBgDark *modeswap.Color = modeswap.New(BrightBlue, gowid.ColorBlue) - PktListCellSelectedFgDark *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorBlack) - PktListCellSelectedBgDark *modeswap.Color = modeswap.New(MediumGray, gowid.ColorWhite) - PktStructSelectedFgDark *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorBlack) - PktStructSelectedBgDark *modeswap.Color = modeswap.New(DarkGray, gowid.ColorWhite) - PktStructFocusBgDark *modeswap.Color = modeswap.New(BrightBlue, gowid.ColorBlue) - HexTopUnselectedFgDark *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorBlue) - HexTopUnselectedBgDark *modeswap.Color = modeswap.New(MediumGray, gowid.ColorWhite) - HexTopSelectedFgDark *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorWhite) - HexTopSelectedBgDark *modeswap.Color = modeswap.New(BrightBlue, gowid.ColorBlue) - HexBottomUnselectedFgDark *modeswap.Color = modeswap.New(gowid.ColorBlack, gowid.ColorBlack) - HexBottomUnselectedBgDark *modeswap.Color = modeswap.New(DarkGray, gowid.ColorWhite) - HexBottomSelectedFgDark *modeswap.Color = modeswap.New(gowid.ColorBlack, gowid.ColorBlack) - HexBottomSelectedBgDark *modeswap.Color = modeswap.New(DarkGray, gowid.ColorWhite) - HexCurUnselectedFgDark *modeswap.Color = modeswap.New(gowid.ColorWhite, gowid.ColorMagenta) - HexCurUnselectedBgDark *modeswap.Color = modeswap.New(gowid.ColorBlack, gowid.ColorWhite) - HexLineFgDark *modeswap.Color = modeswap.New(gowid.ColorBlack, gowid.ColorWhite) - HexLineBgDark *modeswap.Color = modeswap.New(DarkGray, gowid.ColorBlack) - FilterValidBgDark *modeswap.Color = modeswap.New(BrightGreen, gowid.ColorGreen) - ButtonBgDark *modeswap.Color = modeswap.New(MediumGray, gowid.ColorWhite) - StreamClientFgDark *modeswap.Color = modeswap.New(LightRed, gowid.ColorWhite) - StreamClientBgDark *modeswap.Color = modeswap.New(DarkRed, gowid.ColorDarkRed) - StreamServerFgDark *modeswap.Color = modeswap.New(LightBlue, gowid.ColorWhite) - StreamServerBgDark *modeswap.Color = modeswap.New(DarkBlue, gowid.ColorBlue) - - DarkModePalette gowid.Palette = gowid.Palette{ - "default": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorBlack), - "title": gowid.MakeForeground(gowid.ColorRed), - "current-capture": gowid.MakeForeground(gowid.ColorWhite), - "pkt-list-row-focus": gowid.MakePaletteEntry(gowid.ColorWhite, PktListRowFocusBgDark), - "pkt-list-cell-focus": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorPurple), - "pkt-list-row-selected": gowid.MakePaletteEntry(PktListRowSelectedFgDark, PktListRowSelectedBgDark), - "pkt-list-cell-selected": gowid.MakePaletteEntry(PktListCellSelectedFgDark, PktListCellSelectedBgDark), - "pkt-struct-focus": gowid.MakePaletteEntry(gowid.ColorWhite, PktStructFocusBgDark), - "pkt-struct-selected": gowid.MakePaletteEntry(PktStructSelectedFgDark, PktStructSelectedBgDark), - "filter-menu-focus": gowid.MakeStyledPaletteEntry(gowid.ColorWhite, gowid.ColorBlack, gowid.StyleBold), - "filter-valid": gowid.MakePaletteEntry(gowid.ColorBlack, FilterValidBgDark), - "filter-invalid": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed), - "filter-intermediate": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorOrange), - "dialog": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorYellow), - "dialog-buttons": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorBlack), - "minibuffer": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorYellow), - "minibuffer-buttons": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorBlack), - "button": gowid.MakePaletteEntry(gowid.ColorBlack, ButtonBgDark), - "button-focus": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorMagenta), - "button-selected": gowid.MakePaletteEntry(PktListCellSelectedFgDark, PktListCellSelectedBgDark), - "progress-default": gowid.MakeStyledPaletteEntry(gowid.ColorWhite, gowid.ColorBlack, gowid.StyleBold), - "progress-complete": gowid.MakeStyleMod(gowid.MakePaletteRef("progress-default"), gowid.MakeBackground(gowid.ColorMagenta)), - "progress-spinner": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorBlack), - "hex-cur-selected": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorMagenta), - "hex-cur-unselected": gowid.MakePaletteEntry(HexCurUnselectedFgDark, HexCurUnselectedBgDark), - "hex-top-selected": gowid.MakePaletteEntry(HexTopSelectedFgDark, HexTopSelectedBgDark), - "hex-top-unselected": gowid.MakePaletteEntry(HexTopUnselectedFgDark, HexTopUnselectedBgDark), - "hex-bottom-selected": gowid.MakePaletteEntry(HexBottomSelectedFgDark, HexBottomSelectedBgDark), - "hex-bottom-unselected": gowid.MakePaletteEntry(HexBottomUnselectedFgDark, HexBottomUnselectedBgDark), - "hexln-selected": gowid.MakePaletteEntry(HexLineFgDark, HexLineBgDark), - "hexln-unselected": gowid.MakePaletteEntry(HexLineFgDark, HexLineBgDark), - "copy-mode-indicator": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorDarkRed), - "copy-mode": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorYellow), - "copy-mode-alt": gowid.MakePaletteEntry(gowid.ColorYellow, gowid.ColorBlack), - "stream-client": gowid.MakePaletteEntry(StreamClientFgDark, StreamClientBgDark), - "stream-server": gowid.MakePaletteEntry(StreamServerFgDark, StreamServerBgDark), - "stream-match": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorYellow), - "stream-search": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorWhite), + // 256 color < 256 color + ButtonBgDark = modeswap.New(dbg("button", MediumGray), gowid.ColorWhite) + ButtonSelectedFgDark = modeswap.New(dfg("button-selected", gowid.ColorWhite), gowid.ColorBlack) + ButtonSelectedBgDark = modeswap.New(dbg("button-selected", MediumGray), gowid.ColorWhite) + PktListRowSelectedFgDark = modeswap.New(dfg("packet-list-row-selected", gowid.ColorWhite), gowid.ColorBlack) + PktListRowSelectedBgDark = modeswap.New(dbg("packet-list-row-selected", DarkGray), gowid.ColorWhite) + PktListRowFocusBgDark = modeswap.New(dbg("packet-list-row-focus", BrightBlue), gowid.ColorBlue) + PktListCellSelectedFgDark = modeswap.New(dfg("packet-list-cell-selected", gowid.ColorWhite), gowid.ColorBlack) + PktListCellSelectedBgDark = modeswap.New(dbg("packet-list-cell-selected", MediumGray), gowid.ColorWhite) + PktStructSelectedFgDark = modeswap.New(dfg("packet-struct-selected", gowid.ColorWhite), gowid.ColorBlack) + PktStructSelectedBgDark = modeswap.New(dbg("packet-struct-selected", DarkGray), gowid.ColorWhite) + PktStructFocusBgDark = modeswap.New(dbg("packet-struct-focus", BrightBlue), gowid.ColorBlue) + HexFieldUnselectedFgDark = modeswap.New(dfg("hex-field-unselected", gowid.ColorWhite), gowid.ColorBlue) + HexFieldUnselectedBgDark = modeswap.New(dbg("hex-field-unselected", MediumGray), gowid.ColorWhite) + HexFieldSelectedFgDark = modeswap.New(dfg("hex-field-selected", gowid.ColorWhite), gowid.ColorWhite) + HexFieldSelectedBgDark = modeswap.New(dbg("hex-field-selected", BrightBlue), gowid.ColorBlue) + HexLayerUnselectedFgDark = modeswap.New(dfg("hex-layer-unselected", gowid.ColorBlack), gowid.ColorBlack) + HexLayerUnselectedBgDark = modeswap.New(dbg("hex-layer-unselected", DarkGray), gowid.ColorWhite) + HexLayerSelectedFgDark = modeswap.New(dfg("hex-layer-selected", gowid.ColorBlack), gowid.ColorBlack) + HexLayerSelectedBgDark = modeswap.New(dbg("hex-layer-selected", DarkGray), gowid.ColorWhite) + HexByteUnselectedFgDark = modeswap.New(dfg("hex-byte-unselected", gowid.ColorWhite), gowid.ColorMagenta) + HexByteUnselectedBgDark = modeswap.New(dbg("hex-byte-unselected", gowid.ColorBlack), gowid.ColorWhite) + HexIntervalSelectedFgDark = modeswap.New(dfg("hex-interval-selected", gowid.ColorBlack), gowid.ColorWhite) + HexIntervalSelectedBgDark = modeswap.New(dbg("hex-interval-selected", DarkGray), gowid.ColorBlack) + HexIntervalUnselectedFgDark = modeswap.New(dfg("hex-interval-unselected", gowid.ColorBlack), gowid.ColorWhite) + HexIntervalUnselectedBgDark = modeswap.New(dbg("hex-interval-unselected", DarkGray), gowid.ColorBlack) + FilterValidBgDark = modeswap.New(dbg("filter-valid", BrightGreen), gowid.ColorGreen) + StreamClientFgDark = modeswap.New(dfg("stream-client", LightRed), gowid.ColorWhite) + StreamClientBgDark = modeswap.New(dbg("stream-client", DarkRed), gowid.ColorDarkRed) + StreamServerFgDark = modeswap.New(dfg("stream-server", LightBlue), gowid.ColorWhite) + StreamServerBgDark = modeswap.New(dbg("stream-server", DarkBlue), gowid.ColorBlue) + + DarkModePalette = gowid.Palette{ + "default": gowid.MakePaletteEntry(dfg("default", gowid.ColorWhite), dbg("default", gowid.ColorBlack)), + "title": gowid.MakeForeground(dfg("title", gowid.ColorRed)), + "current-capture": gowid.MakeForeground(dfg("current-capture", gowid.ColorWhite)), + "packet-list-row-focus": gowid.MakePaletteEntry(dfg("packet-list-row-focus", gowid.ColorWhite), PktListRowFocusBgDark), + "packet-list-row-selected": gowid.MakePaletteEntry(PktListRowSelectedFgDark, PktListRowSelectedBgDark), + "packet-list-cell-focus": gowid.MakePaletteEntry(dfg("packet-list-cell-focus", gowid.ColorWhite), dbg("packet-list-cell-focus", gowid.ColorPurple)), + "packet-list-cell-selected": gowid.MakePaletteEntry(PktListCellSelectedFgDark, PktListCellSelectedBgDark), + "packet-struct-focus": gowid.MakePaletteEntry(dfg("packet-struct-focus", gowid.ColorWhite), PktStructFocusBgDark), + "packet-struct-selected": gowid.MakePaletteEntry(PktStructSelectedFgDark, PktStructSelectedBgDark), + "filter-menu": gowid.MakeStyledPaletteEntry(dfg("filter-menu", gowid.ColorWhite), dbg("filter-menu", gowid.ColorBlack), gowid.StyleBold), + "filter-valid": gowid.MakePaletteEntry(dfg("filter-valid", gowid.ColorBlack), FilterValidBgDark), + "filter-invalid": gowid.MakePaletteEntry(dfg("filter-invalid", gowid.ColorBlack), dbg("filter-invalid", gowid.ColorRed)), + "filter-intermediate": gowid.MakePaletteEntry(dfg("filter-intermediate", gowid.ColorBlack), dbg("filter-intermediate", gowid.ColorOrange)), + "dialog": gowid.MakePaletteEntry(dfg("dialog", gowid.ColorBlack), dbg("dialog", gowid.ColorYellow)), + "dialog-buttons": gowid.MakePaletteEntry(dfg("dialog-button", gowid.ColorYellow), dbg("dialog-button", gowid.ColorBlack)), + "cmdline": gowid.MakePaletteEntry(dfg("cmdline", gowid.ColorBlack), dbg("cmdline", gowid.ColorYellow)), + "cmdline-buttons": gowid.MakePaletteEntry(dfg("cmdline-button", gowid.ColorYellow), dbg("cmdline-button", gowid.ColorBlack)), + "button": gowid.MakePaletteEntry(dfg("button", gowid.ColorBlack), ButtonBgDark), + "button-focus": gowid.MakePaletteEntry(dfg("button-focus", gowid.ColorWhite), dbg("button-focus", gowid.ColorMagenta)), + "button-selected": gowid.MakePaletteEntry(ButtonSelectedFgDark, ButtonSelectedBgDark), + "progress-default": gowid.MakeStyledPaletteEntry(dfg("progress", gowid.ColorWhite), dbg("progress", gowid.ColorBlack), gowid.StyleBold), + "progress-complete": gowid.MakeStyleMod(gowid.MakePaletteRef("progress-default"), gowid.MakeBackground(dbg("progress-complete", gowid.ColorMagenta))), + "progress-spinner": gowid.MakePaletteEntry(dfg("spinner", gowid.ColorYellow), dbg("spinner", gowid.ColorBlack)), + "hex-byte-selected": gowid.MakePaletteEntry(dfg("hex-byte-selected", gowid.ColorWhite), dbg("hex-byte-selected", gowid.ColorMagenta)), + "hex-byte-unselected": gowid.MakePaletteEntry(HexByteUnselectedFgDark, HexByteUnselectedBgDark), + "hex-field-selected": gowid.MakePaletteEntry(HexFieldSelectedFgDark, HexFieldSelectedBgDark), + "hex-field-unselected": gowid.MakePaletteEntry(HexFieldUnselectedFgDark, HexFieldUnselectedBgDark), + "hex-layer-selected": gowid.MakePaletteEntry(HexLayerSelectedFgDark, HexLayerSelectedBgDark), + "hex-layer-unselected": gowid.MakePaletteEntry(HexLayerUnselectedFgDark, HexLayerUnselectedBgDark), + "hex-interval-selected": gowid.MakePaletteEntry(HexIntervalSelectedFgDark, HexIntervalSelectedBgDark), + "hex-interval-unselected": gowid.MakePaletteEntry(HexIntervalUnselectedFgDark, HexIntervalUnselectedBgDark), + "copy-mode-label": gowid.MakePaletteEntry(dfg("copy-mode-label", gowid.ColorWhite), dbg("copy-mode-label", gowid.ColorDarkRed)), + "copy-mode": gowid.MakePaletteEntry(dfg("copy-mode", gowid.ColorBlack), dbg("copy-mode", gowid.ColorYellow)), + "copy-mode-alt": gowid.MakePaletteEntry(dfg("copy-mode-alt", gowid.ColorYellow), dbg("copy-mode-alt", gowid.ColorBlack)), + "stream-client": gowid.MakePaletteEntry(StreamClientFgDark, StreamClientBgDark), + "stream-server": gowid.MakePaletteEntry(StreamServerFgDark, StreamServerBgDark), + "stream-match": gowid.MakePaletteEntry(dfg("stream-match", gowid.ColorBlack), dbg("stream-match", gowid.ColorYellow)), + "stream-search": gowid.MakePaletteEntry(dfg("stream-search", gowid.ColorBlack), dbg("stream-search", gowid.ColorWhite)), } -) + +} + +func dfg(key string, fb gowid.IColor) gowid.IColor { + return tomlCol(key, theme.Foreground, "dark", fb) +} + +func dbg(key string, fb gowid.IColor) gowid.IColor { + return tomlCol(key, theme.Background, "dark", fb) +} + +func lfg(key string, fb gowid.IColor) gowid.IColor { + return tomlCol(key, theme.Foreground, "light", fb) +} + +func lbg(key string, fb gowid.IColor) gowid.IColor { + return tomlCol(key, theme.Background, "light", fb) +} + +func tomlCol(key string, layer theme.Layer, hue string, fb gowid.IColor) gowid.IColor { + col, err := theme.MakeColorSafe(fmt.Sprintf("themes.rules.%s.%s", hue, key), layer) + if err == nil { + return col + } + return fb +} //====================================================================== // Local Variables: diff --git a/ui/ui.go b/ui/ui.go index 8f2145e..39acc19 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -527,8 +527,8 @@ func makeStructNodeDecoration(pos tree.IPos, tr tree.IModel, wmaker tree.IWidget res = expander.New( isselected.New( res, - styled.New(res, gowid.MakePaletteRef("pkt-struct-selected")), - styled.New(res, gowid.MakePaletteRef("pkt-struct-focus")), + styled.New(res, gowid.MakePaletteRef("packet-struct-selected")), + styled.New(res, gowid.MakePaletteRef("packet-struct-focus")), ), ) @@ -1878,10 +1878,10 @@ func makePacketListModel(psml psmlInfo, app gowid.IApp) *psmlmodel.Model { Style: table.StyleOptions{ VerticalSeparator: fill.New(' '), HeaderStyleProvided: true, - HeaderStyleFocus: gowid.MakePaletteRef("pkt-list-cell-focus"), + HeaderStyleFocus: gowid.MakePaletteRef("packet-list-cell-focus"), CellStyleProvided: true, - CellStyleSelected: gowid.MakePaletteRef("pkt-list-cell-selected"), - CellStyleFocus: gowid.MakePaletteRef("pkt-list-cell-focus"), + CellStyleSelected: gowid.MakePaletteRef("packet-list-cell-selected"), + CellStyleFocus: gowid.MakePaletteRef("packet-list-cell-focus"), }, Layout: table.LayoutOptions{ Widths: []gowid.IWidgetDimension{ @@ -1899,7 +1899,7 @@ func makePacketListModel(psml psmlInfo, app gowid.IApp) *psmlmodel.Model { expandingModel := psmlmodel.New( packetPsmlTableModel, - gowid.MakePaletteRef("pkt-list-row-focus"), + gowid.MakePaletteRef("packet-list-row-focus"), ) if len(expandingModel.Comparators) > 0 { expandingModel.Comparators[0] = table.IntCompare{} @@ -1947,8 +1947,8 @@ func setPacketListWidgets(psml psmlInfo, app gowid.IApp) { packetListView = NewPsmlTableRowWidget( NewRowFocusTableWidget( packetListTable, - "pkt-list-row-selected", - "pkt-list-row-focus", + "packet-list-row-selected", + "packet-list-row-focus", ), psml.PsmlColors(), ) @@ -2103,10 +2103,10 @@ func getHexWidgetToDisplay(row int) *hexdumper2.Widget { layers := getLayersFromStructWidget(row, 0) res2 = hexdumper2.New(b, hexdumper2.Options{ StyledLayers: layers, - CursorUnselected: "hex-cur-unselected", - CursorSelected: "hex-cur-selected", - LineNumUnselected: "hexln-unselected", - LineNumSelected: "hexln-selected", + CursorUnselected: "hex-byte-unselected", + CursorSelected: "hex-byte-selected", + LineNumUnselected: "hex-interval-unselected", + LineNumSelected: "hex-interval-selected", PaletteIfCopying: "copy-mode", }) @@ -2764,7 +2764,7 @@ func Build() (*gowid.App, error) { null.New(), CopyModePredicate, ), - gowid.MakePaletteRef("copy-mode-indicator"), + gowid.MakePaletteRef("copy-mode-label"), ) //====================================================================== diff --git a/widgets/filter/filter.go b/widgets/filter/filter.go index 4e1afe9..c37f829 100644 --- a/widgets/filter/filter.go +++ b/widgets/filter/filter.go @@ -108,7 +108,7 @@ func New(opt Options) *Widget { menuListBox2 := styled.New( framed.NewUnicode(cellmod.Opaque(filterActivator)), - gowid.MakePaletteRef("filter-menu-focus"), + gowid.MakePaletteRef("filter-menu"), ) drop := menu.New("filter", menuListBox2, gowid.RenderWithUnits{U: opt.MaxCompletions + 2}, @@ -414,7 +414,7 @@ func newMenuWidgets(ed *edit.Widget, completions []string) []gowid.IWidget { SelectKeys: []gowid.IKey{gowid.MakeKeyExt(tcell.KeyEnter)}, }, ) - clickmeStyled := styled.NewInvertedFocus(clickme, gowid.MakePaletteRef("filter-menu-focus")) + clickmeStyled := styled.NewInvertedFocus(clickme, gowid.MakePaletteRef("filter-menu")) clickme.OnClick(gowid.MakeWidgetCallback(gowid.ClickCB{}, func(app gowid.IApp, target gowid.IWidget) { txt := ed.Text() end := ed.CursorPos() diff --git a/widgets/minibuffer/minibuffer.go b/widgets/minibuffer/minibuffer.go index 20526d3..8b49a9d 100644 --- a/widgets/minibuffer/minibuffer.go +++ b/widgets/minibuffer/minibuffer.go @@ -163,8 +163,8 @@ func New() *Widget { Buttons: []dialog.Button{}, NoShadow: true, NoFrame: false, - BackgroundStyle: gowid.MakePaletteRef("minibuffer"), - ButtonStyle: gowid.MakePaletteRef("minibuffer-buttons"), + BackgroundStyle: gowid.MakePaletteRef("cmdline"), + ButtonStyle: gowid.MakePaletteRef("cmdline-buttons"), }, ), compl: top, @@ -382,8 +382,8 @@ func (w *Widget) updateCompletions(app gowid.IApp) { complWidgets = append(complWidgets, isselected.New( compBtn, - styled.New(compBtn, gowid.MakePaletteRef("minibuffer-buttons")), - styled.New(compBtn, gowid.MakePaletteRef("minibuffer-buttons")), + styled.New(compBtn, gowid.MakePaletteRef("cmdline-buttons")), + styled.New(compBtn, gowid.MakePaletteRef("cmdline-buttons")), ), ) From 4aef081370931c1ba52fa7c3d829a939c89c6657 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 16:06:37 -0400 Subject: [PATCH 09/31] Latest gowid - so that vim.KeyPress implements gowid.IKey Then I can use vim package KeyPresses in the lists of keys to be handled specially by the display filter widget. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 00eb0ab..630cb8d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/fsnotify/fsnotify v1.4.7 github.com/gcla/deep v1.0.2 - github.com/gcla/gowid v1.1.1-0.20200907041935-63bc5705d2f5 + github.com/gcla/gowid v1.1.1-0.20200907200338-77f36d79ee81 github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359 github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29 github.com/go-test/deep v1.0.2 // indirect diff --git a/go.sum b/go.sum index 9d36e6c..58ac2f7 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/gcla/gowid v1.1.1-0.20200801025312-b6db1a298dd9 h1:5vki4zDKlsADgPf8ZL github.com/gcla/gowid v1.1.1-0.20200801025312-b6db1a298dd9/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= github.com/gcla/gowid v1.1.1-0.20200907041935-63bc5705d2f5 h1:IWHYS/s3P3/RdFzelGZLhSd4NZcetUHP3dRMD7X5igo= github.com/gcla/gowid v1.1.1-0.20200907041935-63bc5705d2f5/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= +github.com/gcla/gowid v1.1.1-0.20200907200338-77f36d79ee81 h1:zqdYVq0clJTH6skJo1CznaWtRmPGwKLk69JnKjm1I1E= +github.com/gcla/gowid v1.1.1-0.20200907200338-77f36d79ee81/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359 h1:3xEhacR7pIJV8daurdBygptxhzTJeYFqJp1V6SDl+pE= github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359/go.mod h1:Wn+pZpM98JHSOYkPDtmdvlqmc0OzQGHWOsHB2d28WtQ= github.com/gcla/tcell v1.1.2-0.20200115035344-b90e69b9dbe0 h1:6fMu73gAbCSAYAGFhMU1G9h5IilNI9wlvEdV4KMF93U= From d755385590e79ee404dc23920294860dd02aa4ff Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 16:07:38 -0400 Subject: [PATCH 10/31] Prefer the functions to direct field access so that I can replace with an interface later if needed --- widgets/mapkeys/mapkeys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widgets/mapkeys/mapkeys.go b/widgets/mapkeys/mapkeys.go index 77fe1a8..6db8d08 100644 --- a/widgets/mapkeys/mapkeys.go +++ b/widgets/mapkeys/mapkeys.go @@ -40,7 +40,7 @@ func (w *Widget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.S var res bool for _, k := range seq { // What should the handled value be?? - res = w.IWidget.UserInput(tcell.NewEventKey(k.Key, k.Ch, k.Mod), size, focus, app) + res = w.IWidget.UserInput(tcell.NewEventKey(k.Key(), k.Rune(), k.Modifiers()), size, focus, app) } return res } else { From 23bd76f11d3534274d019737c27f19c280d0203d Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 16:08:08 -0400 Subject: [PATCH 11/31] Have the display filter respect all typical up/down keys Ctrl-n and Ctrl-p will move up and down the list, but special treatment is needed to move from the filter edit box to the first list entry; here I am now handling Ctrl-n in the same was as down arrow, and then when at the top of the list, Ctrl-p the same way as up arrow. --- widgets/filter/filter.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/widgets/filter/filter.go b/widgets/filter/filter.go index c37f829..7ba6594 100644 --- a/widgets/filter/filter.go +++ b/widgets/filter/filter.go @@ -19,6 +19,7 @@ import ( "github.com/gcla/gowid" "github.com/gcla/gowid/gwutil" + "github.com/gcla/gowid/vim" "github.com/gcla/gowid/widgets/button" "github.com/gcla/gowid/widgets/cellmod" "github.com/gcla/gowid/widgets/columns" @@ -111,15 +112,20 @@ func New(opt Options) *Widget { gowid.MakePaletteRef("filter-menu"), ) + ign := make([]gowid.IKey, 0, len(vim.AllDownKeys)+len(vim.AllUpKeys)) + for _, k := range vim.AllDownKeys { + ign = append(ign, k) + } + for _, k := range vim.AllUpKeys { + ign = append(ign, k) + } + drop := menu.New("filter", menuListBox2, gowid.RenderWithUnits{U: opt.MaxCompletions + 2}, menu.Options{ IgnoreKeysProvided: true, - IgnoreKeys: []gowid.IKey{ - gowid.MakeKeyExt(tcell.KeyUp), - gowid.MakeKeyExt(tcell.KeyDown), - }, - CloseKeysProvided: true, - CloseKeys: []gowid.IKey{}, + IgnoreKeys: ign, + CloseKeysProvided: true, + CloseKeys: []gowid.IKey{}, }, ) @@ -627,7 +633,7 @@ func (w *Widget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid. // it go orange briefly, which is unpleasant. func (w *Widget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { if evk, ok := ev.(*tcell.EventKey); ok { - if evk.Key() == tcell.KeyTAB || evk.Key() == tcell.KeyDown { + if evk.Key() == tcell.KeyTAB || vim.KeyIn(evk, vim.AllDownKeys) { return false } } @@ -650,7 +656,7 @@ type activatorWidget struct { func (w *activatorWidget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { if ev, ok := ev.(*tcell.EventKey); ok && !w.active { - if ev.Key() == tcell.KeyDown { + if vim.KeyIn(ev, vim.AllDownKeys) { w.active = true return true } else { @@ -660,7 +666,7 @@ func (w *activatorWidget) UserInput(ev interface{}, size gowid.IRenderSize, focu res := w.IWidget.UserInput(ev, size, focus, app) if !res { if ev, ok := ev.(*tcell.EventKey); ok && w.active { - if ev.Key() == tcell.KeyUp { + if vim.KeyIn(ev, vim.AllUpKeys) { w.active = false return true } else { From df99ba1899207f6524184f747086c2319baf5d6d Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 17:38:14 -0400 Subject: [PATCH 12/31] I broke the ability to enter j and k in the display filter ... because they were registered as up/down motion keys --- utils.go | 6 ++++++ widgets/filter/filter.go | 14 +++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/utils.go b/utils.go index dde0039..182eab3 100644 --- a/utils.go +++ b/utils.go @@ -28,6 +28,7 @@ import ( "syscall" "text/template" "time" + "unicode" "github.com/adam-hanna/arrayOperations" "github.com/blang/semver" @@ -36,6 +37,7 @@ import ( "github.com/gcla/gowid/vim" "github.com/gcla/termshark/v2/system" "github.com/gcla/termshark/v2/widgets/resizable" + "github.com/gdamore/tcell" "github.com/mattn/go-isatty" "github.com/pkg/errors" "github.com/shibukawa/configdir" @@ -380,6 +382,10 @@ func TailCommand() []string { return ConfStringSlice("main.tail-command", def) } +func KeyPressIsPrintable(key gowid.IKey) bool { + return unicode.IsPrint(key.Rune()) && key.Modifiers() & ^tcell.ModShift == 0 +} + type KeyMapping struct { From vim.KeyPress To vim.KeySequence diff --git a/widgets/filter/filter.go b/widgets/filter/filter.go index 7ba6594..ca85e51 100644 --- a/widgets/filter/filter.go +++ b/widgets/filter/filter.go @@ -114,10 +114,14 @@ func New(opt Options) *Widget { ign := make([]gowid.IKey, 0, len(vim.AllDownKeys)+len(vim.AllUpKeys)) for _, k := range vim.AllDownKeys { - ign = append(ign, k) + if !termshark.KeyPressIsPrintable(k) { + ign = append(ign, k) + } } for _, k := range vim.AllUpKeys { - ign = append(ign, k) + if !termshark.KeyPressIsPrintable(k) { + ign = append(ign, k) + } } drop := menu.New("filter", menuListBox2, gowid.RenderWithUnits{U: opt.MaxCompletions + 2}, @@ -633,7 +637,7 @@ func (w *Widget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid. // it go orange briefly, which is unpleasant. func (w *Widget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { if evk, ok := ev.(*tcell.EventKey); ok { - if evk.Key() == tcell.KeyTAB || vim.KeyIn(evk, vim.AllDownKeys) { + if evk.Key() == tcell.KeyTAB || (vim.KeyIn(evk, vim.AllDownKeys) && !termshark.KeyPressIsPrintable(evk)) { return false } } @@ -656,7 +660,7 @@ type activatorWidget struct { func (w *activatorWidget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { if ev, ok := ev.(*tcell.EventKey); ok && !w.active { - if vim.KeyIn(ev, vim.AllDownKeys) { + if vim.KeyIn(ev, vim.AllDownKeys) && !termshark.KeyPressIsPrintable(ev) { w.active = true return true } else { @@ -666,7 +670,7 @@ func (w *activatorWidget) UserInput(ev interface{}, size gowid.IRenderSize, focu res := w.IWidget.UserInput(ev, size, focus, app) if !res { if ev, ok := ev.(*tcell.EventKey); ok && w.active { - if vim.KeyIn(ev, vim.AllUpKeys) { + if vim.KeyIn(ev, vim.AllUpKeys) && !termshark.KeyPressIsPrintable(ev) { w.active = false return true } else { From 3bb91282c8b6a7a7ac949025dc725708092e7f58 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 17:43:21 -0400 Subject: [PATCH 13/31] Run the 1.15 Go build first That's the one I care about, and the one that publishes build artifacts. --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3653a8f..b724272 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,10 @@ branches: git: depth: false go: -- 1.12.x -- 1.13.x -- 1.14.x - 1.15.x +- 1.14.x +- 1.13.x +- 1.12.x notifications: email: true before_install: From a6ce236dfff377cfbfdbb5bcd8fe452c2928180a Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 18:20:51 -0400 Subject: [PATCH 14/31] Guesswork to make the travis tests more reliable One of the steps is failing unpredictably (to me) --- scripts/simple-tests.sh | 60 ++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/scripts/simple-tests.sh b/scripts/simple-tests.sh index 21eaa88..17d79dd 100755 --- a/scripts/simple-tests.sh +++ b/scripts/simple-tests.sh @@ -2,9 +2,13 @@ set -e +# Safe enough... +PCAP=$(mktemp -u /tmp/testXXXX.pcap) +FIFO=$(mktemp -u /tmp/fifoXXXX) + cleanup() { - rm /tmp/test.pcap - rm /tmp/fifo + rm "${PCAP}" + rm "${FIFO}" } trap cleanup EXIT @@ -17,7 +21,7 @@ go install ./... echo Making a test pcap. -cat < /tmp/test.pcap +cat < "${PCAP}" d4c3b2a102000400 0000000000000000 0000040006000000 @@ -53,28 +57,28 @@ echo Running termshark cli tests. export TS="$GOPATH/bin/termshark" # stdout is not a tty, so falls back to tshark -$TS -r /tmp/test.pcap | grep '192.168.44.213 TFTP 77' +$TS -r "${PCAP}" | grep '192.168.44.213 TFTP 77' # prove that options provided are passed through to tshark -[[ $($TS -r /tmp/test.pcap -T psml -n | grep '' | wc -l) == 2 ]] +[[ $($TS -r "${PCAP}" -T psml -n | grep '' | wc -l) == 2 ]] # Must choose either a file or an interface -! $TS -r /tmp/test.pcap -i eth0 +! $TS -r "${PCAP}" -i eth0 # only display the second line via tshark -[[ $($TS -r /tmp/test.pcap 'frame.number == 2' | wc -l) == 1 ]] +[[ $($TS -r "${PCAP}" 'frame.number == 2' | wc -l) == 1 ]] # test fifos -mkfifo /tmp/fifo -cat /tmp/test.pcap > /tmp/fifo & -$TS -r /tmp/fifo | grep '192.168.44.213 TFTP 77' +mkfifo "${FIFO}" +cat "${PCAP}" > "${FIFO}" & +$TS -r "${FIFO}" | grep '192.168.44.213 TFTP 77' wait -rm /tmp/fifo +rm "${FIFO}" # Check pass-thru option works. Make termshark run in a tty to ensure it's taking effect -[[ $(script -q -e -c "$TS -r /tmp/test.pcap --pass-thru" | wc -l) == 2 ]] +[[ $(script -q -e -c "$TS -r "${PCAP}" --pass-thru" | wc -l) == 2 ]] -[[ $(script -q -e -c "$TS -r /tmp/test.pcap --pass-thru=true" | wc -l) == 2 ]] +[[ $(script -q -e -c "$TS -r "${PCAP}" --pass-thru=true" | wc -l) == 2 ]] # run in script so termshark thinks it's in a tty cat version.go | grep -o -E "v[0-9]+\.[0-9]+(\.[0-9]+)?" | \ @@ -89,40 +93,40 @@ in_tty() { echo UI test 1 # Load a pcap, quit -{ sleep 5s ; echo q ; echo ; } | in_tty $TS -r /tmp/test.pcap +{ sleep 5s ; echo q ; echo ; } | in_tty $TS -r "${PCAP}" echo UI test 2 # Run with stdout not a tty, but disable the pass-thru to tshark -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r /tmp/test.pcap --pass-thru=false | cat" +{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}" --pass-thru=false | cat" echo UI test 3 # Load a pcap, very rudimentary scrape for an IP, quit -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r /tmp/test.pcap" | grep -a 192.168.44.123 > /dev/null +{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}"" | grep -a 192.168.44.123 > /dev/null # Ensure -r flag isn't needed -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS /tmp/test.pcap" | grep -a 192.168.44.123 > /dev/null +{ sleep 5s ; echo q ; echo ; } | in_tty "$TS "${PCAP}"" | grep -a 192.168.44.123 > /dev/null echo UI test 4 # Load a pcap from stdin -{ sleep 5s ; echo q ; echo ; } | in_tty "cat /tmp/test.pcap | TERM=xterm $TS -i -" -{ sleep 5s ; echo q ; echo ; } | in_tty "cat /tmp/test.pcap | TERM=xterm $TS -r -" -{ sleep 5s ; echo q ; echo ; } | in_tty "cat /tmp/test.pcap | TERM=xterm $TS" +{ sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -i -" +{ sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -r -" +{ sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS" echo UI test 5 # Display filter at end of command line { sleep 5s ; echo q ; echo ; } | in_tty "$TS -r scripts/pcaps/telnet-cooked.pcap \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null echo UI test 6 -mkfifo /tmp/fifo -cat /tmp/test.pcap > /tmp/fifo & -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r /tmp/fifo" +mkfifo "${FIFO}" +cat "${PCAP}" > "${FIFO}" & +{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r "${FIFO}"" wait -cat /tmp/test.pcap > /tmp/fifo & -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -i /tmp/fifo" +cat "${PCAP}" > "${FIFO}" & +{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -i "${FIFO}"" wait -cat /tmp/test.pcap > /tmp/fifo & -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS /tmp/fifo" -#{ sleep 5s ; echo q ; echo ; } | in_tty "$TS /tmp/fifo \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null +cat "${PCAP}" > "${FIFO}" & +{ sleep 5s ; echo q ; echo ; } | in_tty "$TS "${FIFO}"" +#{ sleep 5s ; echo q ; echo ; } | in_tty "$TS "${FIFO}" \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null wait echo Tests were successful. From 3bdedd98b9670d0e0ccd5824fb8ba6f1746eae25 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 19:02:00 -0400 Subject: [PATCH 15/31] Another guess to fix the unpredictable test failures --- scripts/simple-tests.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/simple-tests.sh b/scripts/simple-tests.sh index 17d79dd..299c610 100755 --- a/scripts/simple-tests.sh +++ b/scripts/simple-tests.sh @@ -94,27 +94,33 @@ in_tty() { echo UI test 1 # Load a pcap, quit { sleep 5s ; echo q ; echo ; } | in_tty $TS -r "${PCAP}" +reset echo UI test 2 # Run with stdout not a tty, but disable the pass-thru to tshark { sleep 5s ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}" --pass-thru=false | cat" +reset echo UI test 3 # Load a pcap, very rudimentary scrape for an IP, quit { sleep 5s ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}"" | grep -a 192.168.44.123 > /dev/null +reset # Ensure -r flag isn't needed { sleep 5s ; echo q ; echo ; } | in_tty "$TS "${PCAP}"" | grep -a 192.168.44.123 > /dev/null +reset echo UI test 4 # Load a pcap from stdin { sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -i -" { sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -r -" { sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS" +reset echo UI test 5 # Display filter at end of command line { sleep 5s ; echo q ; echo ; } | in_tty "$TS -r scripts/pcaps/telnet-cooked.pcap \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null +reset echo UI test 6 mkfifo "${FIFO}" @@ -128,5 +134,6 @@ cat "${PCAP}" > "${FIFO}" & { sleep 5s ; echo q ; echo ; } | in_tty "$TS "${FIFO}"" #{ sleep 5s ; echo q ; echo ; } | in_tty "$TS "${FIFO}" \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null wait +reset echo Tests were successful. From 416c91378fc7ee600e7ed8301df111a10dc350b9 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 22:11:13 -0400 Subject: [PATCH 16/31] Speed up the simple UI tests This is crude - just grep the log file for a very general "load complete" message, rather than waiting 5s and hoping that is enough time for the pcap load. --- scripts/simple-tests.sh | 34 ++++++++++++++++------------------ ui/ui.go | 1 + 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/scripts/simple-tests.sh b/scripts/simple-tests.sh index 299c610..ee68ab7 100755 --- a/scripts/simple-tests.sh +++ b/scripts/simple-tests.sh @@ -91,49 +91,47 @@ in_tty() { socat - EXEC:"bash -c \\\"stty rows 50 cols 80 && TERM=xterm && $ARGS\\\"",pty,setsid,ctty } +wait_for_load() { + rm ~/.cache/termshark/termshark.log > /dev/null 2>&1 + tail -F ~/.cache/termshark/termshark.log 2> /dev/null | while [ 1 ] ; do read ; grep "Load operation complete" <<<$REPLY && break ; done +} + echo UI test 1 # Load a pcap, quit -{ sleep 5s ; echo q ; echo ; } | in_tty $TS -r "${PCAP}" -reset +{ wait_for_load ; echo q ; echo ; } | in_tty $TS -r "${PCAP}" > /dev/null echo UI test 2 # Run with stdout not a tty, but disable the pass-thru to tshark -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}" --pass-thru=false | cat" -reset +{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}" --pass-thru=false | cat" > /dev/null echo UI test 3 # Load a pcap, very rudimentary scrape for an IP, quit -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}"" | grep -a 192.168.44.123 > /dev/null -reset +{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}"" | grep -a 192.168.44.123 > /dev/null # Ensure -r flag isn't needed -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS "${PCAP}"" | grep -a 192.168.44.123 > /dev/null -reset +{ wait_for_load ; echo q ; echo ; } | in_tty "$TS "${PCAP}"" | grep -a 192.168.44.123 > /dev/null echo UI test 4 # Load a pcap from stdin -{ sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -i -" -{ sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -r -" -{ sleep 5s ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS" -reset +{ wait_for_load ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -i -" > /dev/null +{ wait_for_load ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -r -" > /dev/null +{ wait_for_load ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS" > /dev/null echo UI test 5 # Display filter at end of command line -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r scripts/pcaps/telnet-cooked.pcap \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null -reset +{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -r scripts/pcaps/telnet-cooked.pcap \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null echo UI test 6 mkfifo "${FIFO}" cat "${PCAP}" > "${FIFO}" & -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -r "${FIFO}"" +{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -r "${FIFO}"" > /dev/null wait cat "${PCAP}" > "${FIFO}" & -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS -i "${FIFO}"" +{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -i "${FIFO}"" > /dev/null wait cat "${PCAP}" > "${FIFO}" & -{ sleep 5s ; echo q ; echo ; } | in_tty "$TS "${FIFO}"" +{ wait_for_load ; echo q ; echo ; } | in_tty "$TS "${FIFO}"" > /dev/null #{ sleep 5s ; echo q ; echo ; } | in_tty "$TS "${FIFO}" \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null wait -reset echo Tests were successful. diff --git a/ui/ui.go b/ui/ui.go index 39acc19..6c16adb 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -1189,6 +1189,7 @@ func (t updatePacketViews) AfterEnd(closeMe chan<- struct{}) { updatePacketListWithData(t.Ld, app) StopEmptyStructViewTimer() StopEmptyHexViewTimer() + log.Infof("Load operation complete") })) } From 880e9a278c66d90e3571e65d9a3956fe3d43e57f Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 22:28:25 -0400 Subject: [PATCH 17/31] Fix up some theme inconsistencies. From some unrecognized color errors in the log that I hadn't spotted. --- ui/dialog.go | 6 +++--- ui/palette.go | 12 ++++++------ ui/ui.go | 8 ++++---- widgets/minibuffer/minibuffer.go | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ui/dialog.go b/ui/dialog.go index 9a20dc6..246e9aa 100644 --- a/ui/dialog.go +++ b/ui/dialog.go @@ -101,7 +101,7 @@ func openMessage(msgt string, openOver gowid.ISettableComposite, focusOnWidget b NoShadow: true, BackgroundStyle: gowid.MakePaletteRef("dialog"), BorderStyle: gowid.MakePaletteRef("dialog"), - ButtonStyle: gowid.MakePaletteRef("dialog-buttons"), + ButtonStyle: gowid.MakePaletteRef("dialog-button"), FocusOnWidget: focusOnWidget, }, ) @@ -140,7 +140,7 @@ func OpenTemplatedDialog(container gowid.ISettableComposite, tmplName string, ap NoShadow: true, BackgroundStyle: gowid.MakePaletteRef("dialog"), BorderStyle: gowid.MakePaletteRef("dialog"), - ButtonStyle: gowid.MakePaletteRef("dialog-buttons"), + ButtonStyle: gowid.MakePaletteRef("dialog-button"), }, ) @@ -165,7 +165,7 @@ func OpenTemplatedDialogExt(container gowid.ISettableComposite, tmplName string, NoShadow: true, BackgroundStyle: gowid.MakePaletteRef("dialog"), BorderStyle: gowid.MakePaletteRef("dialog"), - ButtonStyle: gowid.MakePaletteRef("dialog-buttons"), + ButtonStyle: gowid.MakePaletteRef("dialog-button"), }, ) dialog.OpenExt(YesNo, container, width, height, app) diff --git a/ui/palette.go b/ui/palette.go index bc7ef73..26fb4a8 100644 --- a/ui/palette.go +++ b/ui/palette.go @@ -158,14 +158,14 @@ func SetupColors() { "filter-invalid": gowid.MakePaletteEntry(lfg("filter-invalid", gowid.ColorBlack), lbg("filter-invalid", gowid.ColorRed)), "filter-intermediate": gowid.MakePaletteEntry(lfg("filter-intermediate", gowid.ColorBlack), lbg("filter-intermediate", gowid.ColorOrange)), "dialog": gowid.MakePaletteEntry(lfg("dialog", gowid.ColorBlack), lbg("dialog", gowid.ColorYellow)), - "dialog-buttons": gowid.MakePaletteEntry(lfg("dialog-buttons", gowid.ColorYellow), lbg("dialog-buttons", gowid.ColorBlack)), + "dialog-button": gowid.MakePaletteEntry(lfg("dialog-button", gowid.ColorYellow), lbg("dialog-button", gowid.ColorBlack)), "cmdline": gowid.MakePaletteEntry(lfg("cmdline", gowid.ColorBlack), lbg("cmdline", gowid.ColorYellow)), - "cmdline-buttons": gowid.MakePaletteEntry(lfg("cmdline-buttons", gowid.ColorYellow), lbg("cmdline-buttons", gowid.ColorBlack)), + "cmdline-button": gowid.MakePaletteEntry(lfg("cmdline-button", gowid.ColorYellow), lbg("cmdline-button", gowid.ColorBlack)), "button": gowid.MakePaletteEntry(lfg("button", gowid.ColorDarkBlue), ButtonBgReg), "button-focus": gowid.MakePaletteEntry(lfg("button-focus", gowid.ColorWhite), lbg("button-focus", gowid.ColorDarkBlue)), "button-selected": gowid.MakePaletteEntry(ButtonSelectedFgReg, ButtonSelectedBgReg), "progress-default": gowid.MakeStyledPaletteEntry(lfg("progress-default", gowid.ColorWhite), lbg("progress-default", gowid.ColorBlack), gowid.StyleBold), - "progress-complete": gowid.MakeStyleMod(gowid.MakePaletteRef("progress-default"), gowid.MakeBackground(gowid.ColorMagenta)), + "progress-complete": gowid.MakeStyleMod(gowid.MakePaletteRef("progress-default"), gowid.MakeBackground(lbg("progress-complete", gowid.ColorMagenta))), "progress-spinner": gowid.MakePaletteEntry(lfg("progress-spinner", gowid.ColorYellow), lbg("progress-spinner", gowid.ColorBlack)), "hex-byte-selected": gowid.MakePaletteEntry(lfg("hex-byte-selected", gowid.ColorWhite), lbg("hex-byte-selected", gowid.ColorMagenta)), "hex-byte-unselected": gowid.MakePaletteEntry(HexByteUnselectedFgReg, HexByteUnselectedBgReg), @@ -235,13 +235,13 @@ func SetupColors() { "filter-invalid": gowid.MakePaletteEntry(dfg("filter-invalid", gowid.ColorBlack), dbg("filter-invalid", gowid.ColorRed)), "filter-intermediate": gowid.MakePaletteEntry(dfg("filter-intermediate", gowid.ColorBlack), dbg("filter-intermediate", gowid.ColorOrange)), "dialog": gowid.MakePaletteEntry(dfg("dialog", gowid.ColorBlack), dbg("dialog", gowid.ColorYellow)), - "dialog-buttons": gowid.MakePaletteEntry(dfg("dialog-button", gowid.ColorYellow), dbg("dialog-button", gowid.ColorBlack)), + "dialog-button": gowid.MakePaletteEntry(dfg("dialog-button", gowid.ColorYellow), dbg("dialog-button", gowid.ColorBlack)), "cmdline": gowid.MakePaletteEntry(dfg("cmdline", gowid.ColorBlack), dbg("cmdline", gowid.ColorYellow)), - "cmdline-buttons": gowid.MakePaletteEntry(dfg("cmdline-button", gowid.ColorYellow), dbg("cmdline-button", gowid.ColorBlack)), + "cmdline-button": gowid.MakePaletteEntry(dfg("cmdline-button", gowid.ColorYellow), dbg("cmdline-button", gowid.ColorBlack)), "button": gowid.MakePaletteEntry(dfg("button", gowid.ColorBlack), ButtonBgDark), "button-focus": gowid.MakePaletteEntry(dfg("button-focus", gowid.ColorWhite), dbg("button-focus", gowid.ColorMagenta)), "button-selected": gowid.MakePaletteEntry(ButtonSelectedFgDark, ButtonSelectedBgDark), - "progress-default": gowid.MakeStyledPaletteEntry(dfg("progress", gowid.ColorWhite), dbg("progress", gowid.ColorBlack), gowid.StyleBold), + "progress-default": gowid.MakeStyledPaletteEntry(dfg("progress-default", gowid.ColorWhite), dbg("progress-default", gowid.ColorBlack), gowid.StyleBold), "progress-complete": gowid.MakeStyleMod(gowid.MakePaletteRef("progress-default"), gowid.MakeBackground(dbg("progress-complete", gowid.ColorMagenta))), "progress-spinner": gowid.MakePaletteEntry(dfg("spinner", gowid.ColorYellow), dbg("spinner", gowid.ColorBlack)), "hex-byte-selected": gowid.MakePaletteEntry(dfg("hex-byte-selected", gowid.ColorWhite), dbg("hex-byte-selected", gowid.ColorMagenta)), diff --git a/ui/ui.go b/ui/ui.go index 6c16adb..78051d1 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -960,7 +960,7 @@ func processCopyChoices(copyLen int, app gowid.IApp) { NoShadow: true, BackgroundStyle: gowid.MakePaletteRef("dialog"), BorderStyle: gowid.MakePaletteRef("dialog"), - ButtonStyle: gowid.MakePaletteRef("dialog-buttons"), + ButtonStyle: gowid.MakePaletteRef("dialog-button"), FocusOnWidget: true, }, ) @@ -992,7 +992,7 @@ func reallyQuit(app gowid.IApp) { NoShadow: true, BackgroundStyle: gowid.MakePaletteRef("dialog"), BorderStyle: gowid.MakePaletteRef("dialog"), - ButtonStyle: gowid.MakePaletteRef("dialog-buttons"), + ButtonStyle: gowid.MakePaletteRef("dialog-button"), }, ) YesNo.Open(appView, units(len(msgt)+20), app) @@ -1250,7 +1250,7 @@ func reallyClear(app gowid.IApp) { NoShadow: true, BackgroundStyle: gowid.MakePaletteRef("dialog"), BorderStyle: gowid.MakePaletteRef("dialog"), - ButtonStyle: gowid.MakePaletteRef("dialog-buttons"), + ButtonStyle: gowid.MakePaletteRef("dialog-button"), }, ) YesNo.Open(mainViewNoKeys, units(len(msgt)+28), app) @@ -2730,7 +2730,7 @@ func Build() (*gowid.App, error) { NoShadow: true, BackgroundStyle: gowid.MakePaletteRef("dialog"), BorderStyle: gowid.MakePaletteRef("dialog"), - ButtonStyle: gowid.MakePaletteRef("dialog-buttons"), + ButtonStyle: gowid.MakePaletteRef("dialog-button"), }, ) diff --git a/widgets/minibuffer/minibuffer.go b/widgets/minibuffer/minibuffer.go index 8b49a9d..6d7724a 100644 --- a/widgets/minibuffer/minibuffer.go +++ b/widgets/minibuffer/minibuffer.go @@ -164,7 +164,7 @@ func New() *Widget { NoShadow: true, NoFrame: false, BackgroundStyle: gowid.MakePaletteRef("cmdline"), - ButtonStyle: gowid.MakePaletteRef("cmdline-buttons"), + ButtonStyle: gowid.MakePaletteRef("cmdline-button"), }, ), compl: top, @@ -382,8 +382,8 @@ func (w *Widget) updateCompletions(app gowid.IApp) { complWidgets = append(complWidgets, isselected.New( compBtn, - styled.New(compBtn, gowid.MakePaletteRef("cmdline-buttons")), - styled.New(compBtn, gowid.MakePaletteRef("cmdline-buttons")), + styled.New(compBtn, gowid.MakePaletteRef("cmdline-button")), + styled.New(compBtn, gowid.MakePaletteRef("cmdline-button")), ), ) From 121bd32581bb9feca1d37a8864c7c77819353941 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Mon, 7 Sep 2020 22:39:56 -0400 Subject: [PATCH 18/31] Properly validate the first argument of a cmdline "map" command This is to prevent the user accidentally remapping e.g. the 'f' key with something like this: :map f8 d Instead the command should be :map d --- ui/lastline.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ui/lastline.go b/ui/lastline.go index 476b4c6..a580c76 100644 --- a/ui/lastline.go +++ b/ui/lastline.go @@ -471,11 +471,15 @@ func (d mapCommand) Run(app gowid.IApp, args ...string) error { if len(args) == 3 { key1 := vim.VimStringToKeys(args[1]) - keys2 := vim.VimStringToKeys(args[2]) - termshark.AddKeyMapping(termshark.KeyMapping{From: key1[0], To: keys2}) - mappings := termshark.LoadKeyMappings() - for _, mapping := range mappings { - d.w.AddMapping(mapping.From, mapping.To, app) + if len(key1) != 1 { + err = fmt.Errorf("Invalid: first map argument must be a single key (got '%s')", args[1]) + } else { + keys2 := vim.VimStringToKeys(args[2]) + termshark.AddKeyMapping(termshark.KeyMapping{From: key1[0], To: keys2}) + mappings := termshark.LoadKeyMappings() + for _, mapping := range mappings { + d.w.AddMapping(mapping.From, mapping.To, app) + } } } else if len(args) == 1 { OpenTemplatedDialogExt(appView, "Key Mappings", fixed, ratio(0.6), app) From 2c52c9d45f19938ac39f7083f03bcb6853e5a470 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Wed, 9 Sep 2020 23:31:51 -0400 Subject: [PATCH 19/31] Try to track down why my tests are still failing --- scripts/simple-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/simple-tests.sh b/scripts/simple-tests.sh index ee68ab7..17005b0 100755 --- a/scripts/simple-tests.sh +++ b/scripts/simple-tests.sh @@ -93,7 +93,7 @@ in_tty() { wait_for_load() { rm ~/.cache/termshark/termshark.log > /dev/null 2>&1 - tail -F ~/.cache/termshark/termshark.log 2> /dev/null | while [ 1 ] ; do read ; grep "Load operation complete" <<<$REPLY && break ; done + tail -F ~/.cache/termshark/termshark.log 2> /dev/null | while [ 1 ] ; do read ; echo Log: $REPLY 1>&2 ; grep "Load operation complete" <<<$REPLY && break ; done } echo UI test 1 From 291e18b10e3681e39531f012d48eb7af116e871a Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Wed, 9 Sep 2020 23:40:32 -0400 Subject: [PATCH 20/31] Need a better approach... In case this is timing related. I need an approach where I can confirm the previous action before initiating the next action. --- scripts/simple-tests.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/simple-tests.sh b/scripts/simple-tests.sh index 17005b0..249fd6f 100755 --- a/scripts/simple-tests.sh +++ b/scripts/simple-tests.sh @@ -98,39 +98,39 @@ wait_for_load() { echo UI test 1 # Load a pcap, quit -{ wait_for_load ; echo q ; echo ; } | in_tty $TS -r "${PCAP}" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty $TS -r "${PCAP}" > /dev/null echo UI test 2 # Run with stdout not a tty, but disable the pass-thru to tshark -{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}" --pass-thru=false | cat" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "$TS -r "${PCAP}" --pass-thru=false | cat" > /dev/null echo UI test 3 # Load a pcap, very rudimentary scrape for an IP, quit -{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -r "${PCAP}"" | grep -a 192.168.44.123 > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "$TS -r "${PCAP}"" | grep -a 192.168.44.123 > /dev/null # Ensure -r flag isn't needed -{ wait_for_load ; echo q ; echo ; } | in_tty "$TS "${PCAP}"" | grep -a 192.168.44.123 > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "$TS "${PCAP}"" | grep -a 192.168.44.123 > /dev/null echo UI test 4 # Load a pcap from stdin -{ wait_for_load ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -i -" > /dev/null -{ wait_for_load ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -r -" > /dev/null -{ wait_for_load ; echo q ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -i -" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS -r -" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "cat "${PCAP}" | TERM=xterm $TS" > /dev/null echo UI test 5 # Display filter at end of command line -{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -r scripts/pcaps/telnet-cooked.pcap \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "$TS -r scripts/pcaps/telnet-cooked.pcap \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null echo UI test 6 mkfifo "${FIFO}" cat "${PCAP}" > "${FIFO}" & -{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -r "${FIFO}"" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "$TS -r "${FIFO}"" > /dev/null wait cat "${PCAP}" > "${FIFO}" & -{ wait_for_load ; echo q ; echo ; } | in_tty "$TS -i "${FIFO}"" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "$TS -i "${FIFO}"" > /dev/null wait cat "${PCAP}" > "${FIFO}" & -{ wait_for_load ; echo q ; echo ; } | in_tty "$TS "${FIFO}"" > /dev/null +{ wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "$TS "${FIFO}"" > /dev/null #{ sleep 5s ; echo q ; echo ; } | in_tty "$TS "${FIFO}" \'frame.number == 2\'" | grep -a "Frame 2: 74 bytes" > /dev/null wait From 85d836a02711fc28b3b7e43093cb1473f26c9cb9 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Thu, 10 Sep 2020 23:38:26 -0400 Subject: [PATCH 21/31] Update to latest gowid This is to pick up the consolidation of vim.KeyPress and gowid.Key. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 630cb8d..acac647 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/fsnotify/fsnotify v1.4.7 github.com/gcla/deep v1.0.2 - github.com/gcla/gowid v1.1.1-0.20200907200338-77f36d79ee81 + github.com/gcla/gowid v1.1.1-0.20200911033444-a8d93a80f850 github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359 github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29 github.com/go-test/deep v1.0.2 // indirect diff --git a/go.sum b/go.sum index 58ac2f7..122054a 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/gcla/gowid v1.1.1-0.20200907041935-63bc5705d2f5 h1:IWHYS/s3P3/RdFzelG github.com/gcla/gowid v1.1.1-0.20200907041935-63bc5705d2f5/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= github.com/gcla/gowid v1.1.1-0.20200907200338-77f36d79ee81 h1:zqdYVq0clJTH6skJo1CznaWtRmPGwKLk69JnKjm1I1E= github.com/gcla/gowid v1.1.1-0.20200907200338-77f36d79ee81/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= +github.com/gcla/gowid v1.1.1-0.20200911033444-a8d93a80f850 h1:pzbWI7a+Z/x+2OGLkXsAbEgNRW6FRDlNhWQ0tOKES0g= +github.com/gcla/gowid v1.1.1-0.20200911033444-a8d93a80f850/go.mod h1:kwHYNePmuaNa60IAkHfd/OfPeZuoSuz7ww+CeA5q/aQ= github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359 h1:3xEhacR7pIJV8daurdBygptxhzTJeYFqJp1V6SDl+pE= github.com/gcla/tail v1.0.1-0.20190505190527-650e90873359/go.mod h1:Wn+pZpM98JHSOYkPDtmdvlqmc0OzQGHWOsHB2d28WtQ= github.com/gcla/tcell v1.1.2-0.20200115035344-b90e69b9dbe0 h1:6fMu73gAbCSAYAGFhMU1G9h5IilNI9wlvEdV4KMF93U= From 78cf195817a88a8a2cfda8f93890fb1d9406f08e Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Thu, 10 Sep 2020 23:39:11 -0400 Subject: [PATCH 22/31] Return true if a toml config key exists This is the thinnest wrapper around viper... At some point it might be best to ditch the abstraction. --- utils.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils.go b/utils.go index 182eab3..d384eac 100644 --- a/utils.go +++ b/utils.go @@ -129,6 +129,10 @@ func DirOfPathCommand(bin string) (string, error) { // harmless) var confMutex sync.Mutex +func ConfKeyExists(name string) bool { + return viper.Get(name) != nil +} + func ConfString(name string, def string) string { confMutex.Lock() defer confMutex.Unlock() From a77ccd84a55a0fcb3baf16e16be9e4f2628dabda Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Thu, 10 Sep 2020 23:44:58 -0400 Subject: [PATCH 23/31] Fixes a bug loading a recent pcap after a clear operation Reproduce like this: - run termshark via termshark -r foo.pcap - select clear from the menu to reset the UI - select foo.pcap from the recents menu Prior to this fix, the pcap would not load. --- pcap/loader.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pcap/loader.go b/pcap/loader.go index 88a46a4..07a0106 100644 --- a/pcap/loader.go +++ b/pcap/loader.go @@ -410,6 +410,7 @@ func (c *Scheduler) RequestLoadPcap(pcap string, displayFilter string, cb interf // the loader is currently reading from a file, the loading *stops*. func (c *Loader) doClearPcapOperation(cb interface{}, fn RunFn) { if c.State() == 0 { + c.clearSource() c.resetData() handleClear(cb) @@ -827,6 +828,14 @@ func (c *Loader) startLoadNewFilter(displayFilter string, cb interface{}) { c.startLoadPsml(cb) } +func (c *Loader) clearSource() { + c.psrcs = make([]IPacketSource, 0) + c.ifaceFile = "" + c.PcapPsml = "" + c.PcapPdml = "" + c.PcapPcap = "" +} + func (c *Loader) startLoadNewFile(pcap string, displayFilter string, cb interface{}) { c.psrcs = []IPacketSource{FileSource{Filename: pcap}} c.ifaceFile = "" From 592f58878eec81d2770de3dbe5736118713e77fb Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Thu, 10 Sep 2020 23:46:16 -0400 Subject: [PATCH 24/31] Return to correct UI view after maximize/minimize The default UI view shows all three packet windows in a vertical pile. If you hit '|', you can cycle between different layouts, and termshark will remember the chosen layout and preserve it across restarts. But, if you switch to a layout that is not the default, then hit '\' to maximize a pane, then hit '\' to un-maximize the pane, the default layout is chosen. Worse, the behavior is inconsistent because the layout is not preserved, so on restarting termshark, you'll be taken back to the layout that was active prior to the max/un-max operation. This change makes sure that termshark returns to the preferred+configured layout after a max/un-max operation. --- ui/ui.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/ui.go b/ui/ui.go index 78051d1..1a80430 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -1697,7 +1697,14 @@ func mainKeyPress(evk *tcell.EventKey, app gowid.IApp) bool { w := mainViewNoKeys.SubWidget() fp := gowid.FocusPath(w) if w == viewOnlyPacketList || w == viewOnlyPacketStructure || w == viewOnlyPacketHex { - mainViewNoKeys.SetSubWidget(mainview, app) + switch termshark.ConfString("main.layout", "mainview") { + case "altview1": + mainViewNoKeys.SetSubWidget(altview1, app) + case "altview2": + mainViewNoKeys.SetSubWidget(altview2, app) + default: + mainViewNoKeys.SetSubWidget(mainview, app) + } if deep.Equal(fp, maxViewPath) == nil { switch w { case viewOnlyPacketList: From df53ec658ae5d5b81e6b88b15dc4a10811c23972 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Thu, 10 Sep 2020 23:53:29 -0400 Subject: [PATCH 25/31] Reconcile vim.KeyPress with gowid.Key --- widgets/filter/filter.go | 8 ++++---- widgets/mapkeys/mapkeys.go | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/widgets/filter/filter.go b/widgets/filter/filter.go index ca85e51..1ef7332 100644 --- a/widgets/filter/filter.go +++ b/widgets/filter/filter.go @@ -114,13 +114,13 @@ func New(opt Options) *Widget { ign := make([]gowid.IKey, 0, len(vim.AllDownKeys)+len(vim.AllUpKeys)) for _, k := range vim.AllDownKeys { - if !termshark.KeyPressIsPrintable(k) { - ign = append(ign, k) + if !termshark.KeyPressIsPrintable(gowid.Key(k)) { + ign = append(ign, gowid.Key(k)) } } for _, k := range vim.AllUpKeys { - if !termshark.KeyPressIsPrintable(k) { - ign = append(ign, k) + if !termshark.KeyPressIsPrintable(gowid.Key(k)) { + ign = append(ign, gowid.Key(k)) } } diff --git a/widgets/mapkeys/mapkeys.go b/widgets/mapkeys/mapkeys.go index 6db8d08..f147829 100644 --- a/widgets/mapkeys/mapkeys.go +++ b/widgets/mapkeys/mapkeys.go @@ -38,7 +38,8 @@ func (w *Widget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.S kp := vim.KeyPressFromTcell(ev) if seq, ok := w.kmap[kp]; ok { var res bool - for _, k := range seq { + for _, vk := range seq { + k := gowid.Key(vk) // What should the handled value be?? res = w.IWidget.UserInput(tcell.NewEventKey(k.Key(), k.Rune(), k.Modifiers()), size, focus, app) } From b51a445335e0a947673724e11eb68a80a3f01d65 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Thu, 10 Sep 2020 23:54:18 -0400 Subject: [PATCH 26/31] Compile-in some simple themes This uses rakyll/statik to compile the toml files into simple blobs that are compiled-in to termshark and made available programmatically via the Golang http FileSystem interface (https://golang.org/pkg/net/http/#FileSystem). The idea here is to provide a small set of default themes, but allow them to be extended via toml files added to ~/.config/termshark/themes/. Real filesystem themes will take precedence. The themes are organized in an attempt to be base16-theme compatible. --- assets/gen.go | 3 + assets/statik/statik.go | 14 +++++ assets/themes/dracula.toml | 112 ++++++++++++++++++++++++++++++++++ assets/themes/solarized.toml | 113 +++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 1 + 6 files changed, 244 insertions(+) create mode 100644 assets/gen.go create mode 100644 assets/statik/statik.go create mode 100644 assets/themes/dracula.toml create mode 100644 assets/themes/solarized.toml diff --git a/assets/gen.go b/assets/gen.go new file mode 100644 index 0000000..d7387cb --- /dev/null +++ b/assets/gen.go @@ -0,0 +1,3 @@ +//go:generate statik -src=. -f + +package assets diff --git a/assets/statik/statik.go b/assets/statik/statik.go new file mode 100644 index 0000000..104648a --- /dev/null +++ b/assets/statik/statik.go @@ -0,0 +1,14 @@ +// Code generated by statik. DO NOT EDIT. + +package statik + +import ( + "github.com/rakyll/statik/fs" +) + + +func init() { + data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x90\x16+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00gen.goUT\x05\x00\x01q\xe6Z_\xd2\xd7O\xcf\xb7JO\xcdK-J,IU(.I,\xc9\xccV\xd0-.J\xb6\xd5S\xd0M\xe3\xe2*HL\xceNLOUH,.N-)\xe6\x02\x04\x00\x00\xff\xffPK\x07\x080\xf3\x8fG5\x00\x00\x00/\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x17\x15+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00 \x00themes/dracula.tomlUT\x05\x00\x01\xaf\xe3Z_\xccW\xcd\x92\xea,\x10\xdd\xfb\x14\x96\xdf\xf6\xcb\xd4\x18\xcd\x8f\x8b\xfb$S\xb3\xe8\x90F\xa9!?E`\xe6\xfa\xf6\xb7\x02\x91\xa8$\x88Sw\xbc\xba<\xdd'\x87n\x1a8.T\xad:,\x97\xbf\x96\xab\xff\xb2\x1d\xae\xd7\xb0Z,\xdeJ\x01Dqx_,\x97\x05t\xf8\xfa\xaa\xe3q\x1e\xc3&]\x9d\xc0\xb5\x067\xd9&\xdfn-\x18kp\x9bn\xb3$\xb6\xe0F\x83I\x9ad\xe9\xda\x82[\x0d\x16i\x91\x16cf\xa2A\xd2\xff2\x0b\xa6\x1a\xc4\x18\xe3\x92X0\xd3 \xcdiNGzn@\x9a$Ib\xc1\xdd\x00\x16y:\xd2\xc1\x80k\n\xf9\x08\x16f\x9d\xaf\x14\xb2\xc2\x82D\x83y\x81;ZZ\xd04,\x8d\xb3\x18\xc6\xda\xd1TT\xee6tgA:\xa8g;bZ\xc7\x81|\xf4\xd8\xd0\xe3\x17\xd3`\x13R\xe8D\xb4&9B\xedD\xf4\xba\xf7\x02\xd1\x0d\xe9\xd5W\xb0\xc7Z\x82\x13\xa4}\xb0\x11P\xef]5\xbd\xeeV\x89\x96\xbb1\xecc\xc2\xcc\xcaE \xef\x03_\x07&]\x8e\xde\xc4#r\xde|913i >\xf4\x98))\x9b\xbe\x907\x9b\xa4\xbf\xb8\xfa\x7f$\xf5\x9d[\x8d\xc9\x11m\x88\xea.(&e\xa4\x98J\xce9\x1dr$R\x17\x11\xa2D\xaa\x92\xb3\x1a\xbd\"\xa6\xbc\xf3\xf4h\xa2\x9a!kR\xa4i\x8fQ\xd5\x94\xe12'B\x04\\\xde\xad\x12q(\x90{\x1b \xb04\x1c%\x04\xd62\"\xd0J%p\x92#\x0fXa\xf7b\xee\x11\xcd*\x19\xf0f\x1fZ\x8c\xc9\xbe\xb3e\x03\x12\xb8\x8b\x94q\x89\"b\xb5DQa\xc9@\xfa[m\x0e\xc7%\xf5\x138+\xbd\xacS\xd3\x06J\x85\xb5\xbao}\xb7%\xf4i\xd7\x94\x03\xfe\x8e\x8a\xa3\xc4\xe9\x81\xf6\x9c\x03KT\xf5\x9dg\xa1gR\x86\xbc\x0c\xd3\xec\xef\xac+\xde\x8c\xe45\xd3,\xe1D\xd5\xbb\xf6 <\xf0\xe8\xea\xf7\xc6%\x87V\xab\xdf0K\xe7pD\xf1-a\xc3\xbc_\xb5\x05\xf2\x812\xe2\xac\x93\x11A\xce\xef\xbb\xe4\x1cv\xb8\xfc\xc6\xf9\x80h\xbe\xa6\xd4M\xf2\xc4>_s\xc3\xb5/J\xef\xa4PD\x06\xd4}-<\x10\x83F\xec\xbcd\xd1\xec\x05v]D\x9a\xaa\xe5(\xa7o\xb9\xa9f\x9f\x88C,\xf0\x18YZ\xd7\xb2\xbaF\xe1\xbf\xf2\xce\xf4\x82\xf2G\x9dN\n\x84*\"\x9ca-\x83.\xae\x81Q\x81$\x87\xd0\xdb{\xe0t\x08\xe2\x06i<\xd5\x96#>\xaf\xca\xd1[z^\x8c2\x0c\xc9$\xbf\xdc\x97~\xcd\xee\xdb\xb3x\xe3l\x7f\x90\xef\xd3\x86b~E\x7f\xdbPL\x8e\xdbV\xd3\xfe\xa1\xa3\xb8\x9e\xc9\xef9\x8a\xb1k>G17c>Gq\xe2<\x8d\xa3\x98\x1f\x98\xe7p\x147\xd7w[\xe2\x01\x8e\xc2\xff\xbc?\xc4Q\xcc\xb9\x82\xf0'*\xdcRLj'\x96\xfeC\x96\xc2\xa3\xfa\x08K\xe1\xb9\xf0\x02,\xc5\x8d\x87\xdd\xef(nW\xfe8G1\xc8>\xb5\xa38s\x08!\xe9\xce\xb3=a(\xe6\xfe>\xce\x1a\n\xcfK4o(\xe6\x1b\xf0s\x86\xe2O\x00\x00\x00\xff\xffPK\x07\x08XeZ\x1e\xf7\x02\x00\x00\x1f\x13\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x17\x15+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00 \x00themes/solarized.tomlUT\x05\x00\x01\xaf\xe3Z_\xd4W\xc9n\xe38\x10\xbd\xfb+\x0c\xcfu4\xb0,[\xcba\x0e\x93\xe9\xee\x9f\x08r(\x91%\x9b\x08\xb5\x80\xa2\x92v\x7f}\x83\xa4E)\xdae F\xfa\xfa\x8a\xaf6\x15\x8bO\x9b*\xabJ\xa4\xdb\x7f\xb7\xbb\xbf\x82\x08]\x17v\x9b\xcds\x99s\x10\xec\x17\xd2\x97\xcdv\x1bC\x89\xfb\xbd>\xb1\xdf\x1fb\xcf\xdf\xd5\xa0k\xc0\xc0\xf3\x8f\x07\x0b\x1e4x\n}\x0cN\x16\xf44\xe8\x9f\x828\xf4,x\xd4`\xe8E\xc7\xa8\xf1y\xd2`\xe4\x81\x0b\xae\x05}\x0d\"bH\x1b\x9f\x81\x06\x13\x9a\xf8\xd8\xf8\x0c5H\x89w8$\x16\x8c4H\xe2c\xec6\x81@\x83O\xa70\xda\xef-\x18\x9b\x94NQ\x1b$\x1a<\x00\xb8QhA\xd3\xb2\x83\x1f\xc6\xb4\xa9\x1dM\x99$p\xc9\xd1\x82\x89I\xc9\xf3\xfc\xd0\x9c\xe4@^\x15f\xbb\xfc\x8fi\xb11V8`\xfb\xa6l\xe4\n\xd9\x80\xed\x7fe;\x0b\xc4!\xe3\x932\xa6p\xc6L\xc2\x80\xf9\x872\xe7\x02\xb2\xf3P\xd4HY\x8bJ\x14|\xc8\xfa]Y\x85\x99\x9d\x8eI\xb7\xe9\xfd\xc2\xe4\x10/P\xc6+r\x9e\xbf\x0fX\xffS\xf3GA\xbc\xea\xd1\xab\xa4\xccUY\xcf\xadc\xda\xef\xee\xef6Qut\xd7\x10\x9c$'U\xd9\xa1\x99Cm\x9a\xa9\xac\xcd+\x91#\x91\xba\xa4\xa5\x11IJ9\xcbp6\x98)\xb8Mq\x06\xab\xbb\x9d\x1b \x96\x17W'\xcd\xe9\xbap5\xc9\x01.\xef\x8a\xe6p\x88\x91\xcf6E 5\xbcJ\x08\xcc\xa4C\xa0\x90\x95\xe8\xe6Z\xf3\xcc\xe2\xd1\x04\xca\x80\xe7\xe755\x19\xc6\x1d\x1d\xa4\x02H\xc5a\xb6\x96\x86\x910.Q8,\x93(R\xa4\x0c\xe4|\xf7\xcd\x8d\xfaH\x7f\x03\xce\xba\x83\xd5g\xd6=\xbc\xd1R\xcc\xaa\xf5\xb9.\x0b\xa5W\x86\xa6]\xf0\xa7\x13_%\x8e\xcd\xff\xe4\xd5\xb1\xe4*\xbb\xe3\xfa(v\xc2\x90\xd3\xe5\xb1\xd5\x1a\xecpGC\xf7\xd9&\x99\x9a\xae\xbf\xea\x1b\xf0\x157_?g}\x07k\xaa\xd7\xcf\xa4u\xc1\xe1\x8a\xe2\xee\x04\x0c\xfb\xbe\xe8\x05\x90W\x94\x0eg\xa5t\x08r\xbe~o\xf6<\xacK\xc3\xeb9\x11\xf9\xfbp\x16\xe6\xf8\xe0\x1ct\xf9\xebr\xf8\xd0\x8aR\x8a\x8a\xc8\x85}\xe8&p#/\x1e\xc5v\x0bD~\x16X\x96\x0e\xc9\xd3\x82co\xc5\xf4so\x7f\x84\x9a\xbc~\xb9YjY\xb0,C1\xbfJ[q\x17s\x9ax\xa5\x14\x08\xa9C8\xc3\xac\xfb\x16\x8d/\xc3\x1b+\x05I.\xb3\xa4\xd6+q\xe3\x95\x08b\x01\xb1\xd9\x0c\x96'\xdez\xe5\xe9\x8f\xfe\xb1\xb8\xca\xb0$\x93\xbc\xfb\xddT\x05\xad\xa7n\xf3\xcc\xd9\xf9\"_\xc6\xa4\xcdTR\x9f#mF\xa6\xf2\xa8\xa9_B\xdb\xf4\xc7\xf7~m\xd3tsZ\xdb\x8c\x8f\xe2\xb4\xb6\xa9y_I\xdbL\xcd\xd4W\xd36\x0br]\x16\xeaa\xdafN`O\xda\xfc\x0e\x00\x00\xff\xffPK\x07\x08l\xb3\x99&\x11\x03\x00\x00J\x14\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x90\x16+Q0\xf3\x8fG5\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\x00\x00\x00\x00gen.goUT\x05\x00\x01q\xe6Z_PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x17\x15+QXeZ\x1e\xf7\x02\x00\x00\x1f\x13\x00\x00\x13\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81r\x00\x00\x00themes/dracula.tomlUT\x05\x00\x01\xaf\xe3Z_PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x17\x15+Ql\xb3\x99&\x11\x03\x00\x00J\x14\x00\x00\x15\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\xb3\x03\x00\x00themes/solarized.tomlUT\x05\x00\x01\xaf\xe3Z_PK\x05\x06\x00\x00\x00\x00\x03\x00\x03\x00\xd3\x00\x00\x00\x10\x07\x00\x00\x00\x00" + fs.Register(data) + } + \ No newline at end of file diff --git a/assets/themes/dracula.toml b/assets/themes/dracula.toml new file mode 100644 index 0000000..aea91ed --- /dev/null +++ b/assets/themes/dracula.toml @@ -0,0 +1,112 @@ + +unused = "#79e11a" + +[dracula] + base00 = "#282a36" + base01 = "#373844" + base02 = "#464752" + base03 = "#565761" + base04 = "#b6b6b2" + base05 = "#ccccc7" + base06 = "#e2e2dc" + base07 = "#f8f8f2" + base08 = "#ff5555" + base09 = "#ffb86c" + base0a = "#f1fa8c" + base0b = "#50fa7b" + base0c = "#8be9fd" + base0d = "#6272a4" + base0e = "#bd93f9" + base0f = "#ff79c6" + black = "dracula.base00" + blue = "dracula.base0d" + cyan = "dracula.base0c" + green = "dracula.base0b" + magenta = "dracula.base0f" + orange = "dracula.base09" + purple = "dracula.base0e" + red = "dracula.base08" + white = "dracula.base07" + yellow = "dracula.base0a" + +[dark] + button = ["dracula.white","dracula.black"] + button-focus = ["dracula.black","dracula.purple"] + button-selected = ["dracula.white","dracula.black"] + cmdline = ["dracula.black","dracula.yellow"] + cmdline-button = ["dracula.yellow","dracula.black"] + copy-mode = ["dracula.black","dracula.yellow"] + copy-mode-alt = ["dracula.yellow","dracula.black"] + copy-mode-label = ["dracula.white","dracula.red"] + current-capture = ["dracula.white","themes.unused"] + dialog = ["dracula.black","dracula.yellow"] + dialog-button = ["dracula.yellow","dracula.black"] + dracula = ["dracula.white","dracula.black"] + filter-intermediate = ["dracula.black","dracula.orange"] + filter-invalid = ["dracula.black","dracula.red"] + filter-menu = ["dracula.white","dracula.black"] + filter-valid = ["dracula.black","dracula.green"] + hex-byte-selected = ["dracula.black","dracula.purple"] + hex-byte-unselected = ["dracula.white","dracula.black"] + hex-field-selected = ["dracula.black","dracula.cyan"] + hex-field-unselected = ["dracula.black","dracula.white"] + hex-interval-selected = ["dracula.white","dracula.base03"] + hex-interval-unselected = ["dracula.white","dracula.base02"] + hex-layer-selected = ["dracula.white","dracula.base03"] + hex-layer-unselected = ["dracula.white","dracula.base02"] + packet-list-cell-focus = ["dracula.black","dracula.purple"] + packet-list-cell-selected = ["dracula.white","dracula.base03"] + packet-list-row-focus = ["dracula.base03","dracula.cyan"] + packet-list-row-selected = ["dracula.white","dracula.base02"] + packet-struct-focus = ["dracula.black","dracula.cyan"] + packet-struct-selected = ["dracula.black","dracula.base03"] + progress-complete = ["dracula.white","dracula.purple"] + progress-dracula = ["dracula.white","dracula.black"] + progress-spinner = ["dracula.yellow","dracula.purple"] + spinner = ["dracula.yellow","dracula.black"] + stream-client = ["dracula.black","dracula.red"] + stream-match = ["dracula.black","dracula.yellow"] + stream-search = ["dracula.black","dracula.white"] + stream-server = ["dracula.cyan","dracula.blue"] + title = ["dracula.red","themes.unused"] + +[light] + button = ["dracula.black","dracula.white"] + button-focus = ["dracula.black","dracula.purple"] + button-selected = ["dracula.black","dracula.base04"] + cmdline = ["dracula.black","dracula.yellow"] + cmdline-button = ["dracula.yellow","dracula.black"] + copy-mode = ["dracula.white","dracula.yellow"] + copy-mode-alt = ["dracula.yellow","dracula.white"] + copy-mode-label = ["dracula.black","dracula.red"] + current-capture = ["dracula.black","themes.unused"] + dialog = ["dracula.black","dracula.yellow"] + dialog-button = ["dracula.yellow","dracula.black"] + dracula = ["dracula.black","dracula.white"] + filter-intermediate = ["dracula.black","dracula.orange"] + filter-invalid = ["dracula.black","dracula.red"] + filter-menu = ["dracula.black","dracula.white"] + filter-valid = ["dracula.black","dracula.green"] + hex-byte-selected = ["dracula.black","dracula.purple"] + hex-byte-unselected = ["dracula.black","dracula.white"] + hex-field-selected = ["dracula.black","dracula.cyan"] + hex-field-unselected = ["dracula.black","dracula.base03"] + hex-interval-selected = ["dracula.white","dracula.base03"] + hex-interval-unselected = ["dracula.black","dracula.base05"] + hex-layer-selected = ["dracula.white","dracula.base03"] + hex-layer-unselected = ["dracula.black","dracula.base05"] + packet-list-cell-focus = ["dracula.black","dracula.purple"] + packet-list-cell-selected = ["dracula.black","dracula.base04"] + packet-list-row-focus = ["dracula.black","dracula.cyan"] + packet-list-row-selected = ["dracula.black","dracula.base05"] + packet-struct-focus = ["dracula.black","dracula.cyan"] + packet-struct-selected = ["dracula.black","dracula.base05"] + progress-complete = ["dracula.white","dracula.purple"] + progress-dracula = ["dracula.white","dracula.black"] + progress-spinner = ["dracula.yellow","dracula.black"] + spinner = ["dracula.yellow","dracula.white"] + stream-client = ["dracula.white","dracula.red"] + stream-match = ["dracula.white","dracula.yellow"] + stream-search = ["dracula.white","dracula.black"] + stream-server = ["dracula.cyan","dracula.blue"] + title = ["dracula.red","themes.unused"] diff --git a/assets/themes/solarized.toml b/assets/themes/solarized.toml new file mode 100644 index 0000000..a36cfc6 --- /dev/null +++ b/assets/themes/solarized.toml @@ -0,0 +1,113 @@ + +unused = "#79e11a" + +[solarized] + base00 = "#002b36" + base01 = "#073642" + base02 = "#586e75" + base03 = "#657b83" + base04 = "#839496" + base05 = "#93a1a1" + base06 = "#eee8d5" + base07 = "#fdf6e3" + base08 = "#dc322f" + base09 = "#cb4b16" + base0a = "#B58900" + base0b = "#859900" + base0c = "#2aa198" + base0d = "#268bd2" + base0e = "#6c71c4" + base0f = "#d33682" + black = "solarized.base00" + blue = "solarized.base0D" + cyan = "solarized.base0C" + green = "solarized.base0B" + magenta = "solarized.base0F" + orange = "solarized.base09" + purple = "solarized.base0E" + red = "solarized.base08" + white = "solarized.base07" + yellow = "solarized.base0A" + +[dark] + button = ["solarized.white","solarized.black"] + button-focus = ["solarized.black","solarized.purple"] + button-selected = ["solarized.white","solarized.black"] + cmdline = ["solarized.black","solarized.yellow"] + cmdline-button = ["solarized.yellow","solarized.black"] + copy-mode = ["solarized.black","solarized.yellow"] + copy-mode-alt = ["solarized.yellow","solarized.black"] + copy-mode-label = ["solarized.white","solarized.red"] + current-capture = ["solarized.white","unused"] + dialog = ["solarized.black","solarized.yellow"] + dialog-button = ["solarized.yellow","solarized.black"] + dracula = ["solarized.white","solarized.black"] + filter-intermediate = ["solarized.black","solarized.orange"] + filter-invalid = ["solarized.black","solarized.red"] + filter-menu = ["solarized.white","solarized.black"] + filter-valid = ["solarized.black","solarized.green"] + hex-byte-selected = ["solarized.black","solarized.purple"] + hex-byte-unselected = ["solarized.white","solarized.black"] + hex-field-selected = ["solarized.black","solarized.cyan"] + hex-field-unselected = ["solarized.black","solarized.white"] + hex-interval-selected = ["solarized.white","solarized.base03"] + hex-interval-unselected = ["solarized.white","solarized.base02"] + hex-layer-selected = ["solarized.white","solarized.base03"] + hex-layer-unselected = ["solarized.white","solarized.base02"] + packet-list-cell-focus = ["solarized.black","solarized.purple"] + packet-list-cell-selected = ["solarized.white","solarized.base03"] + packet-list-row-focus = ["solarized.base03","solarized.cyan"] + packet-list-row-selected = ["solarized.white","solarized.base02"] + packet-struct-focus = ["solarized.black","solarized.cyan"] + packet-struct-selected = ["solarized.black","solarized.base03"] + progress-complete = ["solarized.white","solarized.purple"] + progress-dracula = ["solarized.white","solarized.black"] + progress-spinner = ["solarized.yellow","solarized.purple"] + spinner = ["solarized.yellow","solarized.black"] + stream-client = ["solarized.black","solarized.red"] + stream-match = ["solarized.black","solarized.yellow"] + stream-search = ["solarized.black","solarized.white"] + stream-server = ["solarized.cyan","solarized.blue"] + title = ["solarized.red","unused"] + +[light] + button = ["solarized.black","solarized.white"] + button-focus = ["solarized.black","solarized.purple"] + button-selected = ["solarized.black","solarized.base04"] + cmdline = ["solarized.black","solarized.yellow"] + cmdline-button = ["solarized.yellow","solarized.black"] + copy-mode = ["solarized.white","solarized.yellow"] + copy-mode-alt = ["solarized.yellow","solarized.white"] + copy-mode-label = ["solarized.black","solarized.red"] + current-capture = ["solarized.black","unused"] + dialog = ["solarized.black","solarized.yellow"] + dialog-button = ["solarized.yellow","solarized.black"] + dracula = ["solarized.black","solarized.white"] + filter-intermediate = ["solarized.black","solarized.orange"] + filter-invalid = ["solarized.black","solarized.red"] + filter-menu = ["solarized.black","solarized.white"] + filter-valid = ["solarized.black","solarized.green"] + hex-byte-selected = ["solarized.black","solarized.purple"] + hex-byte-unselected = ["solarized.black","solarized.white"] + hex-field-selected = ["solarized.black","solarized.cyan"] + hex-field-unselected = ["solarized.black","solarized.base03"] + hex-interval-selected = ["solarized.white","solarized.base03"] + hex-interval-unselected = ["solarized.black","solarized.base05"] + hex-layer-selected = ["solarized.white","solarized.base03"] + hex-layer-unselected = ["solarized.black","solarized.base05"] + packet-list-cell-focus = ["solarized.black","solarized.purple"] + packet-list-cell-selected = ["solarized.black","solarized.base04"] + packet-list-row-focus = ["solarized.black","solarized.cyan"] + packet-list-row-selected = ["solarized.black","solarized.base05"] + packet-struct-focus = ["solarized.black","solarized.cyan"] + packet-struct-selected = ["solarized.black","solarized.base05"] + progress-complete = ["solarized.white","solarized.purple"] + progress-dracula = ["solarized.white","solarized.black"] + progress-spinner = ["solarized.yellow","solarized.black"] + spinner = ["solarized.yellow","solarized.white"] + stream-client = ["solarized.white","solarized.red"] + stream-match = ["solarized.white","solarized.yellow"] + stream-search = ["solarized.white","solarized.black"] + stream-server = ["solarized.cyan","solarized.blue"] + title = ["solarized.red","unused"] + diff --git a/go.mod b/go.mod index acac647..a1a6d52 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/mreiferson/go-snappystream v0.2.3 github.com/pkg/errors v0.8.1 github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 + github.com/rakyll/statik v0.1.6 github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 github.com/sirupsen/logrus v1.4.2 github.com/spf13/viper v1.3.2 diff --git a/go.sum b/go.sum index 122054a..1bf96ac 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,7 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w= github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y= From 2edf53dd12877081fc68f4d31545514579ececcf Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Thu, 10 Sep 2020 23:58:20 -0400 Subject: [PATCH 27/31] Load the configured theme at startup Termshark will now look in termshark.toml for a main.theme key. If set to e.g. foobar, then termshark will attempt to load (1) ~/.config/termshark/themes/foobar.toml, and failing that (2) see if it has a built-in theme called /themes/foobar.toml via its statik filesystem. If it finds the toml, regardless of the contents, the theme will be active and called foobar. Then when loading its color palette, any config-file references to colors e.g. dark.progress-bar - will look up a key dark.progress bar in the toml loaded from foobar.toml. If, eventually, a value is found that can be parsed as a color, termshark will use it for that UI element. --- cmd/termshark/termshark.go | 9 ++++ theme/utils.go | 87 ++++++++++++++++++++++++++++++-------- ui/palette.go | 13 +++++- 3 files changed, 90 insertions(+), 19 deletions(-) diff --git a/cmd/termshark/termshark.go b/cmd/termshark/termshark.go index b188562..940e346 100644 --- a/cmd/termshark/termshark.go +++ b/cmd/termshark/termshark.go @@ -26,6 +26,7 @@ import ( "github.com/gcla/termshark/v2/pcap" "github.com/gcla/termshark/v2/streams" "github.com/gcla/termshark/v2/system" + "github.com/gcla/termshark/v2/theme" "github.com/gcla/termshark/v2/tty" "github.com/gcla/termshark/v2/ui" "github.com/gcla/termshark/v2/widgets/filter" @@ -675,6 +676,14 @@ func cmain() int { ui.AutoScroll = termshark.ConfBool("main.auto-scroll", true) ui.PacketColors = termshark.ConfBool("main.packet-colors", true) + themeName := termshark.ConfString("main.theme", "") + if themeName != "" { + err = theme.Load(themeName) + if err != nil { + log.Warnf("Theme %s could not be loaded: %v", themeName, err) + } + } + // Set them up here so they have access to any command-line flags that // need to be passed to the tshark commands used pdmlArgs := termshark.ConfStringSlice("main.pdml-args", []string{}) diff --git a/theme/utils.go b/theme/utils.go index 53dd9d2..a2af271 100644 --- a/theme/utils.go +++ b/theme/utils.go @@ -6,9 +6,17 @@ package theme import ( + "fmt" + "io" + "os" + "path/filepath" + "github.com/gcla/gowid" - "github.com/gcla/termshark/v2" - log "github.com/sirupsen/logrus" + "github.com/rakyll/statik/fs" + "github.com/shibukawa/configdir" + "github.com/spf13/viper" + + _ "github.com/gcla/termshark/v2/assets/statik" ) //====================================================================== @@ -20,6 +28,8 @@ const ( Background Layer = iota ) +var theme *viper.Viper + // MakeColorSafe extends gowid's MakeColorSafe function, prefering to interpret // its string argument as a toml file config key lookup first; if this fails, then // fall back to gowid.MakeColorSafe, which will then read colors as urwid color names, @@ -27,32 +37,73 @@ const ( func MakeColorSafe(s string, l Layer) (gowid.Color, error) { loops := 10 cur := s - for { - next := termshark.ConfString(cur, "") - if next != "" { - cur = next - } else { - next := termshark.ConfStringSlice(cur, []string{}) - if len(next) != 2 { - break + if theme != nil { + for { + next := theme.GetString(cur) + if next != "" { + cur = next } else { - cur = next[l] + next := theme.GetStringSlice(cur) + if next == nil || len(next) != 2 { + break + } else { + cur = next[l] + } + } + loops -= 1 + if loops == 0 { + break } - } - loops -= 1 - if loops == 0 { - break } } col, err := gowid.MakeColorSafe(cur) if err == nil { return gowid.Color{IColor: col, Id: s}, nil } - col, err = gowid.MakeColorSafe(s) + return gowid.MakeColorSafe(s) +} + +// Clear resets the package-level theme object. Next time ui.SetupColors is called, +// the theme-connected colors won't be found, and termshark will fall back to its +// programmed default colors. +func Clear() { + theme = nil +} + +// Load will set the package-level theme object to a viper object representing the +// toml file either (a) read from disk, or failing that (b) built-in to termshark. +// Disk themes are prefered and take precedence. +func Load(name string) error { + theme = viper.New() + theme.SetConfigType("toml") + stdConf := configdir.New("", "termshark") + dirs := stdConf.QueryFolders(configdir.Global) + + // Prefer to load from disk + themeFileName := filepath.Join(dirs[0].Path, "themes", fmt.Sprintf("%s.toml", name)) + + var file io.ReadCloser + var err error + + file, err = os.Open(themeFileName) + if err == nil { + defer file.Close() + return theme.ReadConfig(file) + } + + // Fall back to built-in themes + statikFS, err := fs.New() if err != nil { - log.Infof("Could not understand configured theme color '%s'", s) + return err } - return col, err + + file, err = statikFS.Open(filepath.Join("/themes", fmt.Sprintf("%s.toml", name))) + if err != nil { + return err + } + defer file.Close() + + return theme.ReadConfig(file) } //====================================================================== diff --git a/ui/palette.go b/ui/palette.go index 26fb4a8..32bb507 100644 --- a/ui/palette.go +++ b/ui/palette.go @@ -9,8 +9,10 @@ import ( "fmt" "github.com/gcla/gowid" + "github.com/gcla/termshark/v2" "github.com/gcla/termshark/v2/theme" "github.com/gcla/termshark/v2/theme/modeswap" + log "github.com/sirupsen/logrus" ) //====================================================================== @@ -280,10 +282,19 @@ func lbg(key string, fb gowid.IColor) gowid.IColor { } func tomlCol(key string, layer theme.Layer, hue string, fb gowid.IColor) gowid.IColor { - col, err := theme.MakeColorSafe(fmt.Sprintf("themes.rules.%s.%s", hue, key), layer) + rule := fmt.Sprintf("%s.%s", hue, key) + col, err := theme.MakeColorSafe(rule, layer) if err == nil { return col + } else { + // Warn if the user has defined themes.rules.etcetc, but the resulting + // color can't be resolved. If no key is present, it means the user hasn't + // set up themes, so ignore. + if termshark.ConfString("main.theme", "") != "" { + log.Infof("Could not understand configured theme color '%s'", key) + } } + return fb } From 2e8bb7417fa9a92eb5dd2284fa165f4251ca4172 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Fri, 11 Sep 2020 00:01:23 -0400 Subject: [PATCH 28/31] Add theme and no-theme minibuffer commands You can run :theme and then select from the options - populated internally from termshark's built-in theme repository, and from ~/.config/termshark/themes/*.toml. The choice is saved under the key main.theme in termshark.toml. :no-theme to cause termshark to use its default colors (and save that preference). --- ui/lastline.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ ui/ui.go | 9 +++++ 2 files changed, 106 insertions(+) diff --git a/ui/lastline.go b/ui/lastline.go index a580c76..0d95ac7 100644 --- a/ui/lastline.go +++ b/ui/lastline.go @@ -7,6 +7,7 @@ package ui import ( "fmt" + "io/ioutil" "path/filepath" "strconv" "strings" @@ -15,10 +16,15 @@ import ( "github.com/gcla/gowid/gwutil" "github.com/gcla/gowid/vim" "github.com/gcla/termshark/v2" + "github.com/gcla/termshark/v2/theme" "github.com/gcla/termshark/v2/widgets/mapkeys" "github.com/gcla/termshark/v2/widgets/minibuffer" "github.com/gdamore/tcell/terminfo" "github.com/gdamore/tcell/terminfo/dynamic" + "github.com/rakyll/statik/fs" + "github.com/shibukawa/configdir" + + _ "github.com/gcla/termshark/v2/assets/statik" ) //====================================================================== @@ -29,6 +35,7 @@ var invalidReadCommandErr = fmt.Errorf("Invalid read command") var invalidRecentsCommandErr = fmt.Errorf("Invalid recents command") var invalidMapCommandErr = fmt.Errorf("Invalid map command") var invalidFilterCommandErr = fmt.Errorf("Invalid filter command") +var invalidThemeCommandErr = fmt.Errorf("Invalid theme command") type minibufferFn func(gowid.IApp, ...string) error @@ -220,6 +227,58 @@ func (s filterArg) Completions() []string { //====================================================================== +type themeArg struct { + substr string +} + +var _ minibuffer.IArg = themeArg{} + +func (s themeArg) OfferCompletion() bool { + return true +} + +func (s themeArg) Completions() []string { + matches := make([]string, 0) + + // First gather built-in themes + statikFS, err := fs.New() + if err == nil { + dir, err := statikFS.Open("/themes") + if err == nil { + info, err := dir.Readdir(-1) + if err == nil { + for _, finfo := range info { + m := strings.TrimSuffix(finfo.Name(), ".toml") + if strings.Contains(m, s.substr) { + matches = append(matches, m) + } + } + } + } + } + + // Then from filesystem + stdConf := configdir.New("", "termshark") + conf := stdConf.QueryFolderContainsFile("themes") + if conf != nil { + files, err := ioutil.ReadDir(filepath.Join(conf.Path, "themes")) + if err == nil { + for _, file := range files { + m := strings.TrimSuffix(file.Name(), ".toml") + if !termshark.StringInSlice(m, matches) { + if strings.Contains(m, s.substr) { + matches = append(matches, m) + } + } + } + } + } + + return matches +} + +//====================================================================== + func stringIn(s string, a []string) bool { for _, s2 := range a { if s == s2 { @@ -460,6 +519,44 @@ func (d filterCommand) Arguments(toks []string) []minibuffer.IArg { //====================================================================== +type themeCommand struct{} + +var _ minibuffer.IAction = themeCommand{} + +func (d themeCommand) Run(app gowid.IApp, args ...string) error { + var err error + + if len(args) != 2 { + err = invalidThemeCommandErr + } else { + termshark.SetConf("main.theme", args[1]) + theme.Load(args[1]) + SetupColors() + } + + if err != nil { + OpenMessage(fmt.Sprintf("Error: %s", err), appView, app) + } + + return err +} + +func (d themeCommand) OfferCompletion() bool { + return true +} + +func (d themeCommand) Arguments(toks []string) []minibuffer.IArg { + res := make([]minibuffer.IArg, 0) + pref := "" + if len(toks) > 0 { + pref = toks[0] + } + res = append(res, themeArg{substr: pref}) + return res +} + +//====================================================================== + type mapCommand struct { w *mapkeys.Widget } diff --git a/ui/ui.go b/ui/ui.go index 1a80430..e4d1a99 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -48,6 +48,7 @@ import ( "github.com/gcla/termshark/v2/pdmltree" "github.com/gcla/termshark/v2/psmlmodel" "github.com/gcla/termshark/v2/system" + "github.com/gcla/termshark/v2/theme" "github.com/gcla/termshark/v2/ui/menuutil" "github.com/gcla/termshark/v2/ui/tableutil" "github.com/gcla/termshark/v2/widgets" @@ -1017,6 +1018,13 @@ func lastLineMode(app gowid.IApp) { return nil })) + MiniBuffer.Register("no-theme", minibufferFn(func(gowid.IApp, ...string) error { + theme.Clear() + termshark.DeleteConf("main.theme") + SetupColors() + return nil + })) + MiniBuffer.Register("convs", minibufferFn(func(gowid.IApp, ...string) error { openConvsUi(app) return nil @@ -1057,6 +1065,7 @@ func lastLineMode(app gowid.IApp) { MiniBuffer.Register("load", readCommand{complete: true}) MiniBuffer.Register("recents", recentsCommand{}) MiniBuffer.Register("filter", filterCommand{}) + MiniBuffer.Register("theme", themeCommand{}) MiniBuffer.Register("map", mapCommand{w: keyMapper}) MiniBuffer.Register("unmap", unmapCommand{w: keyMapper}) MiniBuffer.Register("help", helpCommand{}) From 558140af9a57b2975d0662663e1c970424bdbe30 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Fri, 11 Sep 2020 00:05:58 -0400 Subject: [PATCH 29/31] Disable my crummy UI tests for now until I understand the failures These run locally for me on Ubuntu, but are failing on Travis. --- scripts/simple-tests.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/simple-tests.sh b/scripts/simple-tests.sh index 249fd6f..d533fbb 100755 --- a/scripts/simple-tests.sh +++ b/scripts/simple-tests.sh @@ -7,8 +7,10 @@ PCAP=$(mktemp -u /tmp/testXXXX.pcap) FIFO=$(mktemp -u /tmp/fifoXXXX) cleanup() { + set +e rm "${PCAP}" rm "${FIFO}" + true } trap cleanup EXIT @@ -100,6 +102,9 @@ echo UI test 1 # Load a pcap, quit { wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty $TS -r "${PCAP}" > /dev/null +echo Tests disabled for now until I understand whats going on with Travis... +exit 0 + echo UI test 2 # Run with stdout not a tty, but disable the pass-thru to tshark { wait_for_load ; sleep 0.5s ; echo q ; sleep 0.5s ; echo ; } | in_tty "$TS -r "${PCAP}" --pass-thru=false | cat" > /dev/null From 13d4b88ce69b7a46ba9f88f9eb6c041b82a1800c Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Fri, 11 Sep 2020 00:12:39 -0400 Subject: [PATCH 30/31] Fix a search-and-replace error that led to theme warnings --- assets/statik/statik.go | 2 +- assets/themes/dracula.toml | 8 ++++---- assets/themes/solarized.toml | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/statik/statik.go b/assets/statik/statik.go index 104648a..82a5644 100644 --- a/assets/statik/statik.go +++ b/assets/statik/statik.go @@ -8,7 +8,7 @@ import ( func init() { - data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x90\x16+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00gen.goUT\x05\x00\x01q\xe6Z_\xd2\xd7O\xcf\xb7JO\xcdK-J,IU(.I,\xc9\xccV\xd0-.J\xb6\xd5S\xd0M\xe3\xe2*HL\xceNLOUH,.N-)\xe6\x02\x04\x00\x00\xff\xffPK\x07\x080\xf3\x8fG5\x00\x00\x00/\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x17\x15+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00 \x00themes/dracula.tomlUT\x05\x00\x01\xaf\xe3Z_\xccW\xcd\x92\xea,\x10\xdd\xfb\x14\x96\xdf\xf6\xcb\xd4\x18\xcd\x8f\x8b\xfb$S\xb3\xe8\x90F\xa9!?E`\xe6\xfa\xf6\xb7\x02\x91\xa8$\x88Sw\xbc\xba<\xdd'\x87n\x1a8.T\xad:,\x97\xbf\x96\xab\xff\xb2\x1d\xae\xd7\xb0Z,\xdeJ\x01Dqx_,\x97\x05t\xf8\xfa\xaa\xe3q\x1e\xc3&]\x9d\xc0\xb5\x067\xd9&\xdfn-\x18kp\x9bn\xb3$\xb6\xe0F\x83I\x9ad\xe9\xda\x82[\x0d\x16i\x91\x16cf\xa2A\xd2\xff2\x0b\xa6\x1a\xc4\x18\xe3\x92X0\xd3 \xcdiNGzn@\x9a$Ib\xc1\xdd\x00\x16y:\xd2\xc1\x80k\n\xf9\x08\x16f\x9d\xaf\x14\xb2\xc2\x82D\x83y\x81;ZZ\xd04,\x8d\xb3\x18\xc6\xda\xd1TT\xee6tgA:\xa8g;bZ\xc7\x81|\xf4\xd8\xd0\xe3\x17\xd3`\x13R\xe8D\xb4&9B\xedD\xf4\xba\xf7\x02\xd1\x0d\xe9\xd5W\xb0\xc7Z\x82\x13\xa4}\xb0\x11P\xef]5\xbd\xeeV\x89\x96\xbb1\xecc\xc2\xcc\xcaE \xef\x03_\x07&]\x8e\xde\xc4#r\xde|913i >\xf4\x98))\x9b\xbe\x907\x9b\xa4\xbf\xb8\xfa\x7f$\xf5\x9d[\x8d\xc9\x11m\x88\xea.(&e\xa4\x98J\xce9\x1dr$R\x17\x11\xa2D\xaa\x92\xb3\x1a\xbd\"\xa6\xbc\xf3\xf4h\xa2\x9a!kR\xa4i\x8fQ\xd5\x94\xe12'B\x04\\\xde\xad\x12q(\x90{\x1b \xb04\x1c%\x04\xd62\"\xd0J%p\x92#\x0fXa\xf7b\xee\x11\xcd*\x19\xf0f\x1fZ\x8c\xc9\xbe\xb3e\x03\x12\xb8\x8b\x94q\x89\"b\xb5DQa\xc9@\xfa[m\x0e\xc7%\xf5\x138+\xbd\xacS\xd3\x06J\x85\xb5\xbao}\xb7%\xf4i\xd7\x94\x03\xfe\x8e\x8a\xa3\xc4\xe9\x81\xf6\x9c\x03KT\xf5\x9dg\xa1gR\x86\xbc\x0c\xd3\xec\xef\xac+\xde\x8c\xe45\xd3,\xe1D\xd5\xbb\xf6 <\xf0\xe8\xea\xf7\xc6%\x87V\xab\xdf0K\xe7pD\xf1-a\xc3\xbc_\xb5\x05\xf2\x812\xe2\xac\x93\x11A\xce\xef\xbb\xe4\x1cv\xb8\xfc\xc6\xf9\x80h\xbe\xa6\xd4M\xf2\xc4>_s\xc3\xb5/J\xef\xa4PD\x06\xd4}-<\x10\x83F\xec\xbcd\xd1\xec\x05v]D\x9a\xaa\xe5(\xa7o\xb9\xa9f\x9f\x88C,\xf0\x18YZ\xd7\xb2\xbaF\xe1\xbf\xf2\xce\xf4\x82\xf2G\x9dN\n\x84*\"\x9ca-\x83.\xae\x81Q\x81$\x87\xd0\xdb{\xe0t\x08\xe2\x06i<\xd5\x96#>\xaf\xca\xd1[z^\x8c2\x0c\xc9$\xbf\xdc\x97~\xcd\xee\xdb\xb3x\xe3l\x7f\x90\xef\xd3\x86b~E\x7f\xdbPL\x8e\xdbV\xd3\xfe\xa1\xa3\xb8\x9e\xc9\xef9\x8a\xb1k>G17c>Gq\xe2<\x8d\xa3\x98\x1f\x98\xe7p\x147\xd7w[\xe2\x01\x8e\xc2\xff\xbc?\xc4Q\xcc\xb9\x82\xf0'*\xdcRLj'\x96\xfeC\x96\xc2\xa3\xfa\x08K\xe1\xb9\xf0\x02,\xc5\x8d\x87\xdd\xef(nW\xfe8G1\xc8>\xb5\xa38s\x08!\xe9\xce\xb3=a(\xe6\xfe>\xce\x1a\n\xcfK4o(\xe6\x1b\xf0s\x86\xe2O\x00\x00\x00\xff\xffPK\x07\x08XeZ\x1e\xf7\x02\x00\x00\x1f\x13\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x17\x15+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00 \x00themes/solarized.tomlUT\x05\x00\x01\xaf\xe3Z_\xd4W\xc9n\xe38\x10\xbd\xfb+\x0c\xcfu4\xb0,[\xcba\x0e\x93\xe9\xee\x9f\x08r(\x91%\x9b\x08\xb5\x80\xa2\x92v\x7f}\x83\xa4E)\xdae F\xfa\xfa\x8a\xaf6\x15\x8bO\x9b*\xabJ\xa4\xdb\x7f\xb7\xbb\xbf\x82\x08]\x17v\x9b\xcds\x99s\x10\xec\x17\xd2\x97\xcdv\x1bC\x89\xfb\xbd>\xb1\xdf\x1fb\xcf\xdf\xd5\xa0k\xc0\xc0\xf3\x8f\x07\x0b\x1e4x\n}\x0cN\x16\xf44\xe8\x9f\x828\xf4,x\xd4`\xe8E\xc7\xa8\xf1y\xd2`\xe4\x81\x0b\xae\x05}\x0d\"bH\x1b\x9f\x81\x06\x13\x9a\xf8\xd8\xf8\x0c5H\x89w8$\x16\x8c4H\xe2c\xec6\x81@\x83O\xa70\xda\xef-\x18\x9b\x94NQ\x1b$\x1a<\x00\xb8QhA\xd3\xb2\x83\x1f\xc6\xb4\xa9\x1dM\x99$p\xc9\xd1\x82\x89I\xc9\xf3\xfc\xd0\x9c\xe4@^\x15f\xbb\xfc\x8fi\xb11V8`\xfb\xa6l\xe4\n\xd9\x80\xed\x7fe;\x0b\xc4!\xe3\x932\xa6p\xc6L\xc2\x80\xf9\x872\xe7\x02\xb2\xf3P\xd4HY\x8bJ\x14|\xc8\xfa]Y\x85\x99\x9d\x8eI\xb7\xe9\xfd\xc2\xe4\x10/P\xc6+r\x9e\xbf\x0fX\xffS\xf3GA\xbc\xea\xd1\xab\xa4\xccUY\xcf\xadc\xda\xef\xee\xef6Qut\xd7\x10\x9c$'U\xd9\xa1\x99Cm\x9a\xa9\xac\xcd+\x91#\x91\xba\xa4\xa5\x11IJ9\xcbp6\x98)\xb8Mq\x06\xab\xbb\x9d\x1b \x96\x17W'\xcd\xe9\xbap5\xc9\x01.\xef\x8a\xe6p\x88\x91\xcf6E 5\xbcJ\x08\xcc\xa4C\xa0\x90\x95\xe8\xe6Z\xf3\xcc\xe2\xd1\x04\xca\x80\xe7\xe755\x19\xc6\x1d\x1d\xa4\x02H\xc5a\xb6\x96\x86\x910.Q8,\x93(R\xa4\x0c\xe4|\xf7\xcd\x8d\xfaH\x7f\x03\xce\xba\x83\xd5g\xd6=\xbc\xd1R\xcc\xaa\xf5\xb9.\x0b\xa5W\x86\xa6]\xf0\xa7\x13_%\x8e\xcd\xff\xe4\xd5\xb1\xe4*\xbb\xe3\xfa(v\xc2\x90\xd3\xe5\xb1\xd5\x1a\xecpGC\xf7\xd9&\x99\x9a\xae\xbf\xea\x1b\xf0\x157_?g}\x07k\xaa\xd7\xcf\xa4u\xc1\xe1\x8a\xe2\xee\x04\x0c\xfb\xbe\xe8\x05\x90W\x94\x0eg\xa5t\x08r\xbe~o\xf6<\xacK\xc3\xeb9\x11\xf9\xfbp\x16\xe6\xf8\xe0\x1ct\xf9\xebr\xf8\xd0\x8aR\x8a\x8a\xc8\x85}\xe8&p#/\x1e\xc5v\x0bD~\x16X\x96\x0e\xc9\xd3\x82co\xc5\xf4so\x7f\x84\x9a\xbc~\xb9YjY\xb0,C1\xbfJ[q\x17s\x9ax\xa5\x14\x08\xa9C8\xc3\xac\xfb\x16\x8d/\xc3\x1b+\x05I.\xb3\xa4\xd6+q\xe3\x95\x08b\x01\xb1\xd9\x0c\x96'\xdez\xe5\xe9\x8f\xfe\xb1\xb8\xca\xb0$\x93\xbc\xfb\xddT\x05\xad\xa7n\xf3\xcc\xd9\xf9\"_\xc6\xa4\xcdTR\x9f#mF\xa6\xf2\xa8\xa9_B\xdb\xf4\xc7\xf7~m\xd3tsZ\xdb\x8c\x8f\xe2\xb4\xb6\xa9y_I\xdbL\xcd\xd4W\xd36\x0br]\x16\xeaa\xdafN`O\xda\xfc\x0e\x00\x00\xff\xffPK\x07\x08l\xb3\x99&\x11\x03\x00\x00J\x14\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x90\x16+Q0\xf3\x8fG5\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\x00\x00\x00\x00gen.goUT\x05\x00\x01q\xe6Z_PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x17\x15+QXeZ\x1e\xf7\x02\x00\x00\x1f\x13\x00\x00\x13\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81r\x00\x00\x00themes/dracula.tomlUT\x05\x00\x01\xaf\xe3Z_PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x17\x15+Ql\xb3\x99&\x11\x03\x00\x00J\x14\x00\x00\x15\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\xb3\x03\x00\x00themes/solarized.tomlUT\x05\x00\x01\xaf\xe3Z_PK\x05\x06\x00\x00\x00\x00\x03\x00\x03\x00\xd3\x00\x00\x00\x10\x07\x00\x00\x00\x00" + data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x90\x16+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00gen.goUT\x05\x00\x01q\xe6Z_\xd2\xd7O\xcf\xb7JO\xcdK-J,IU(.I,\xc9\xccV\xd0-.J\xb6\xd5S\xd0M\xe3\xe2*HL\xceNLOUH,.N-)\xe6\x02\x04\x00\x00\xff\xffPK\x07\x080\xf3\x8fG5\x00\x00\x00/\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x94\x16+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00 \x00statik/statik.goUT\x05\x00\x01x\xe6Z_\xbcX[[S\xc9\x12}&\xbf\"\"\n\x08\x84\xdd\xfb\xda\xed0\x8c\x0e \x10\x14\x04\xf1\x06\xcd`_1\x03\x04H\x02\xd3:\xcao?\xdf\xeaj\x1c\xf1\xfb\xceQ\xe7\xe1<\x84\x90\xbdw\xba\xabV\xadZ\xb5:\xf3\xf3\xed\xa53\xeb\xdaG\xae\xef\x06j\xe4l[\x7fh\x0fGj\xd4;\xee\xb4\x97\xb7\xda\x9b[\xbb\xed\x95\xe5\xf5\xddN\xabu\xae\xcc\xb1:r\xe9n\xab\xd5;=?\x1b\x8c\xdaS\xad\xb1\xf1\xa3\xde\xe8\xfd\xa5\xee\x98\xb3\xd3\xf9\x81:\xfepr2O\x0f\xcd\xfb\xe1xk\xba\xd5j\xf9\xcb\xbei\xf7\xfa\xbd\xd1\xd4t\xfb\xef\xd6\x98U#\xd5~\xf8k{\xfc\xf9\x86\x0cY!CV\xca\xc0J\x19\xb2L\x86\x8c\xdf~\x17\x99\x0c\xac\x9e\xd9NW\x7f\xe6U\xe3}\x0c\x7f\x8e\\\xbfst\xf6rW\x86\xacJ7\xd9\x85\x0c\xae\xde;\x94\xc1\xe62\xd8fK\x06\xe3e\xd0M\x17\xff\xd9\x8d\xb9\xee\xec\xfa\xcb\xa9\xce\xfa\xac\x0cF\xc8`\xcc+\x19l6\xd7\xe9\xca\xa0k\x19l\xf5\x02\x9f\x9f\xc9\xe0\n\x19\\\xfe`\xed\xa9\x0c\xc6m>\xddz\xb96\xdb\xd9\x9c\x9b\xc6\xea2d9ew\x13\x93\xf7x\xc5\xbc\x1b\xe4\x88K\x85\x0c\xdc\xafV_\x07?\xff\xf5\x87\x1fA\x8952\xb0\xea\xdf\xa0\xc4\x8a/(\x8d\xde\xbbS7\x9c\xb7\x03e.OTgtvzr\x1b2\x19\x94G\xc2@\xcd\x98\xd7\xc0I\x06\x91\xcb\xe0\xd4\xac\x0c,\x93\xc1Z\x19\xbc\xa68\x05`B\xbe\xb5\x0cF\xcb`\x11?\xa7/q/\x03\xd7xv\xe2\x85\x0c\x1a\x10r\xd4\xfa\x89\x0cJ\xdc\xf9m\xe5\x1d\xc1\xe7\x15}]7\x04\xa5@\x08|B\x06\xce_\xfc%\x8362h\xb5\x80m'e\xe0M_\x06\xa6xgW\x06e\x1f\xce\xca \x1a\x19\xb4\xa7H\x94&\xf4u.\x03\xb32(\x87\xaa\xcb\xa0\xb3\xbdY\x19\xac\xeb\"\xc1\xe5\x8bp\x98\xbe\x97U#\x19<\xa7\x18\x94B\xda\x1720'\x83)\xee\x1f\xc8 \xac\x0c&\x93A\x03\x9d\xba\x91\xc1\x8a\xfb\xc8\xb7?\x87,\x8f\xcfe\x10\xba\x8f\xd4&\x880\x0e\xb9\xf1b]\x06\xa1\xac\x0cN\xc8`\x95\x0c<\xdf\x97!\xb3\xe0x\x8f\xf2c5x\xa8\xf2\xc7\xc4L\xefA!-\x83\xaa\x91\x9d\x0c&\xc1\x08\xde\x89\xfc\x0d@/\xda@\xb5\xb7\xb9\xfa\xb1\xff\x08\xebO\xack\x19\x0c\xa3r\xc4\"\xd7\x1f\x1e\xd2r\xb8\xca\xb3c\xd9\x97\xc1\x0b\xe2\x0f\xab=\xa5\x83\xe2\xa2p\x80\xc8\xe4\x88m\x19!\x7f\x90\x81\xb3_\xf6\xf6\xc0\xf7rV\x06n\xa9b\xb1\x965\xa5a\xd9\xee\xae\x0c\xce\xd5\xa3\xa3\xc7\x0fQ\xa3\xa3_\xf4\x9e\x0c\xa6\xc1w?\xc9\xe0K\x19,\xc7\n\x148\xf8j\x8bw\xa0\xdf\x0ej\xbf,\x83.\xef\x8b\xdfepv\x99\x1e\xd7\xa8}\xaa\xbbe\x04RD\xadz\x8d\xaaa\xf1=\xc4H\x14V\xe5g\xba\xca\xd8s\x19\x9c?\xa8d\xd0\xf8\x86{%\x03\x17\xc4\x01\xad\x99\x0c\xce\x18J/\xbe\xab\x956\x1eG\x87\x1d\xa2)QZ\x0ej\xa0\xd0\xe5\xdd\x01\xfe\xfb$X\xd1k/RX\x82OO\xa3\xb62h\x07\xc66\xf4A\x95D5\x9d\x18\xd3\xf8 \x19|\x05`\xf7 2\xa8\x08c\xa7\xe0.\x1a\xa63u\xdf\xd1\xd7\x04\xefB:p\xdb\x0e&vR3\x83\xe9\xc0\x1f\xd4C\x8bE\xc4ULj\x9c\xc8\x00\xf6C<|\xf9\x1e\xcf\xa2\xf0w\x8ew\xb0f\x0f\x0d\xb6\x0d\xa8d\x10\xa5\x0c\x8e\xe5\x93\xbfG\x01\x91\x94\x19\x18\xc8\xf2\x8b)$\xf0\xb7\x0cL\xb7\x01\x1e\x88e\xee\x91\xd0\xd8:\xc7>6\xeb\xde\x03\x95\xf3\xbb2d\xfeM,\x89\x06\xaa\x14\xa1\xb1\x0fd`B\x06\x9f\x81;\x1e\x051\x94*\xd0\xd1\x85#\xe9b9!\x83\x96\x17\xe5E\xac\xc88 \xac\x96\xb7Ai\xf1\x08\xa8\xed\x9f\xca\x90\x01\xf7\xe6\x1ea\xc7\n>CeT\x06r\x0b\xa9\xaa\xbb2\xf0\x8a\xfaN\xab3T=>\x8e\xa4\xd1\xcf\xc8\x17[zG\x85\xe4h\xdf\x82\xda\x06\x18sF\x92\"\x0c\x9e\xdb\xd8\xbd\xa9\xd2\x91\x0c\x8a\x1d\xed\xc8\xc0\x13\xb4\x99\xa1\x1d\x9d!\x82(3C\xe0!CWV\xb8;\x0bl\x97 h\x0d\x81\xa9\xc7\x16\x00\x06\xc9\x99K\x0c6\xf5=\xa8\xd3+\x12!\xeb\xb3\x0d\x19\\s\x0e\x9e\xb39\xa4_`\xc7\xc3\xa4%\x95\x0chy\x0e\xcc\\\x8e}e\x10\xc0\x90=\x06M(\x18l\xe6b\xbd\xae\x12\xe7\x0c5#\x9a\x9ag\xef \x7f\xb0\xc4\x96\xcfhAS.\x1e\x0e\xd3f\xd5|7%U>_\xa6Ii\xcb\xcfs\x0b$\xe2\xbcxBikH\xb5e\xf4!\xab\xae\x0e\x96A\xb2$\x87\xd5\x94\x0c\xaa9\x93A\x0b\x8864\x04\xa2\xce\x97f \x01\xc6\xdf\xee%\x89E\xf5\x15\xd6d\xd4$1\x1cG\xfd\x84\x06\xf6\xf9*J\xb0 U\xe2\xe5\x03\x10O\x185\x87H:\xa4\xd4\x9cm\xe3\x0f\x94\xbf!\x15\xb1\xfao\x88\xea\x884\x0c@euo!q\xbe\xbe\xbbHrf\xa26\xed\x7f\xfc\x03e\xcb\xa9\xa8FLP\x18\x16\xc86\xd74\x91@i\x0bJ\x16\x01\x12u\x82\x16&\xf7\x01\x9c@\x04^\xeb\xeb\x15\xbal\xf5\xf3\xa7I'\xf4+\xba\x0b\xbe)F\\CA\x84\xa3>p\xb1\"h\\v\xbc\xb8\xca\x1a\xb3\xb8\n\xe7\x91/\x90*\xe0q\x01\x11\xf6\xf4\xee\x9as\x880\x92l\xfe\xdaO\x891\xda\xcb\xe4i\x84\x99\xdfP\xcem\x920\xc0\x1fA\xcc&\x1f \xa7\x9d\xa7\x7fN\x92\xdcy\xb7D\xff\xe0\x8b\xd8\x08\xc2\x94\xf1\x8dT\x08Au\xcarX\x9c\x8a\xc2\x89\xe8\x02\x0d?\xd5\x7f\x8d\x15\xf8*\xda\x9c/\x129U\xc1\x87X\xe2\x0e\xf5\x92\x89=\xfe\xab\x9aJ\xd3\xda-\xd25\xa6PI\xe37\xca\xb3t\x8b\xa19\xb2!u\x97\xcb\xb7n{\x90o]\xd1\x1b\xb7G\xa3\xf6F\xff\xbf\x98\x15\x7fcX\xfe\x0f\xc6\xa8\xfa\xd6\x18\x0d\xcfN\xd4\xa0\xf7\xd1\xd9\xefX#[\xc2\x1a\x89>>sj)\x1d}\xd1L\"\xa0\xbf\xc4\xb4\xc8f\xf7a\x8c\x14\x89\x1eZ\x1c\x98\x82\x87h\xa4\x8c\x0f \xcf\xec\x1e\xcd\x17\xe4\x83\n\xc01F\xad\xcf\xaf@\xc5\xcf\xe8\x11\xb4\xf1\xca4\x06\xb1k?I\xa6I%\xe5\x83\xd4!\x13\xae\xb7\xb0\xd0\x03hP\x97\xe6\x0e\xd8\xdex2W\xd0\x13\xb4\x04\x88\x94\xf1\x03\xe0vE\x1b\x1b;\x94A\x88!\xe5\x01!\x88\xb3;'\x97d\xec\x15\x8a\xbbD3\x16\xceO[p\x85\x91\xf5c\xb8\x02;\x83\x0f\xe8L\x95\x1d\x93m2_\x9co*:L\xa3+\x83\xec\x7f\x06H\x9b\xb0%\x10\x8829\xc3\x18\x1bt\xae\x9c\x0d@\x18\x06\x91\xaf\x90\xc3P\xb8\xce> \xaaw$\x8b\x10\xf9\xe8\x990\x7f\xaa\xcf\xd1<\x8c\xeb5\xe2a\\\x8b\x91\xea\x81LP4\x18=\xf8\x13\xbcg\xa6ZC>\x7f\xc1h\"\nn\xca5\xb0\x16\xba\x0f\x8d\xe5\xec\x11`\xdf\x82\xfee\xe4\x7f\x9c\x9f#C$\xe2\x84\xdb\xdc\xc6V\xf8:\xdc)\x18\xa2\xf9\xf6\xfb\xc7\xa4\x14\x90C\x14\x0dd\xc6r\xba\x84~b\xfa?\x03\xd2\x13\xe7$\x1c\xd0]\x94\x83\x8bu\xba\x10g\xbd!\xf5\x13q\x06=\xfa\x03\xc5M\xe3\x00\xb7\xb8\xef\x01|\xf6\x8a\xbf\xa3j\xa8\xfa$\"\xd2\x87?%\xf68\x8b\xb2\xbb_\x08!S\xdeIV\xb2\x80H\xd4\xd8\xbc~\x9a\xfc_\x96\xe6I\x83Q\x94Z\x11\xd1\xfb\xe29j\xb0\xf6\x16\xbc\xea\xa2\xf9>A$@\xbb\x83\xb74\x9e\x85 O\xc9\xdd:1\x0c\xdc\xf6\x96\x16\x8e\x93+\x9b\x7f\x8e\xbdf\x06\xa4\x95`_\xe6\xdf@\x08^`\x87\xd5\xc74\x86\x9d\"(T2Y\xc6\xbc|K\x9cR\xd0o\x02\x9f\xda\xc6\xf9z\xfbrD3\x87E\x90&&_R\xe2Pf!\x96N\xd3\xfc\x124V\x8d\x9dA\x8b\xdd%\xe3\x8dN\xc4\x0e\n\xce\x83\xadw!p\xfa\xbc\x8e\x96\x0f\x05|vA\x9cA$\x00\x1c\xe91=F\x12\xcb\x9a\xd7\x93i\x90\xc0\xfc\xa8\xf3\x8a\x8a\x96\xb1\x0e\x05\x88nt\x80\x17\xf6\x0f\xdb\x19_\xaf\xb4\xe1\x1bL\x97\xda\x1bb\xae\xca%4 \x0d\x1eQ%\x1fQ\xefQ\xf5\xf1\x84\xbb\xb1\xc3%M\xb7XR\xe0\xdbT\x15y1p\n'\x9cx\xca\x81,\xe6k\x10xE\xe7\x10\x04\x0b\x11\x16,\xebls\x1cv\x8a\xa9\x9d\xf4`$\xd5\xa7dZ,M\x04\xaf\xd6H&\xa0\xb4Q\xf0\x15\xd1\xd7VG\xb0\x89\xbfR\x99,\xdbI\x19(rTZtR\x0f\xc2\xafcGU\x1f\xd0\xc4Q\xa8Oqx/M5K\x03\x00\xa4\x88>\n~\xa3|\x90\x1dG\xab+\x00\xbd|\x8d%\xdfP\x81\xb3d>\xa2m\xc9\xcd\x19\x0dx_\x0e\xcf\xa8\x96\xbc$\x80\xb4\xb9F\xa5\xde\xfe\xf96\xce\xa3\xa5\xe8\xcf\xba\xfb8\xe86\x90}\x15R;\x94DQ%\x968\xe5\xa3\x12\x1eQ\x1f\xed<]dq\xc2U\xeb\x1d:\xae\xc4!S\xcf\\$\x81\xc1\xac\xc4\x01\x00\xc32VO\x10\xac\xa2\x9e\x84\xb1\xfeHu\x8d\x1e\xdd\x93\x9f\xc2S\xf0R\xe0\xb9\xce&hH\xea\xb4\xb3\xb5i\x00\xe30\xa3p\xf6\xbfi\x11,\xec\x85\x1c?\xa4^\x88\xaaaww\xa0\xfcwO\x9f\x90-\x86\x05U1\xa3\xc3\xdfi\x14\xc2\x8cb\x9a\xf8\xe6\xfa\x14\xc8\x8d\x86{t\x83\x1b\x8a\x08t\x80V\xa3\x8b\x94\xf8p\xb8\x8e\xdbO\x89\xc0q\xe4\xdb\x02\xd0\xeb\xc1A*n$\x97\xf2\x9b\xef\x16voV\xc2\x19\xbf\xb8\x84\xd2\xd1zL]\x14t\xbe\x03~\xf1\xdc\x14\xa7\xd1..\xc7\x83\x95\x8dn\xd1\x0f \xc3\x1d\x9c\xa4\x1a\x02\xd7\xe6z\x13\xab\xce&+V\x12\xca\xf0\x08\xc6\xce\xd18\xc0\x84Q\xc5\xe9\xdeC7\x8d:NR\xb7D\x0d\xabR\xe3\xf1\x7f\x1c9H\x81\xfc\x84\xf8D7\xd4?\xe0\xbdX\xdc\"\xad\xc5\xb2`\xd7\x7f\xb3n'\x14\x9d@\x1b\x82\xee\xd9\x17\xb7\xd6\xfd\xca\xa4\x91uc4QX:\xb1}\xef\x17\xc0\x1f\xf9\xad\xec\xd6\xaf~\xdf\x7f\xe9\x1b\xafp\xeb\xfa\xff\xf8\xb5\xf0g\xe2\xbe\xb1\x9e?\xe8fo\xfd\x14\xf7\xfd\x17\x85>\xf8\xfa\xe2O\xfc~\xf7o\xf2\xf8\xc1\xd2\xder\xce\xdf\x7f\xdd\x94\x00K\xff\xb3\xe2O9\xee\x98LuS\xfao^\xc5\xedw[\xdc\xbe\x0f\x9f\x10\x99{\xeb{\xe3\xad\xb11?\xec\xec\xb8\xa3\xdep\xe4\x06SV\x8d\xd4tk\xecsk\xec?\x01\x00\x00\xff\xffPK\x07\x08\xb0\xa8\xa2\xa7{\n\x00\x002\x17\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x81!+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00 \x00themes/dracula.tomlUT\x05\x00\x01\x12\xf9Z_\xccW\xcdr\xe2<\x10\xbc\xf3\x14\x14\xdf\xf5s*\x18\xfc\xc3a\x9f$\x95\xc3X\x1e\x81+\xf2O\xc9R\xb2\xbc\xfd\x96%#\x83e\x0b9\xb5a\xe1\xd83\xed\xd6\x8c\xa4Q\xb3\x92\x95l1_\xffZo\xfeK\x0e\xb8\xdd\xc2f\xb5z\xcb9\x10\xc9\xe0}\xb5^g\xd0\xe2\xeb\xab\x8a\x87i\x08\xbbxs\x01\xb7\n\xdc%\xbbt\xbf7`\xa8\xc0}\xbcO\xa2\xd0\x80;\x05Fq\x94\xc4[\x03\xee\x15\x98\xc5Y\x9c\x0d\x99\x91\x02I\xf7K\x0c\x18+\x10C\x0csb\xc0D\x814\xa5)\x1d\xe8\xa9\x06i\x14E\x91\x01\x0f=\x98\xa5\xf1@\x07\x0dn)\xa4\x03\x98\xe9u\xbeRH2\x03\x12\x05\xa6\x19\x1ehn@\xdd\xb08LB\x18jG]Q~\xd8\xd1\x83\x01i\xaf\x9e\x1c\x88n\x1d\x03\xf2\xd1a}\x8f_t\x83uH\xa2\x15Q\x9a\xe4\x0c\x95\x15Q\xeb>rD;\xa4V_\xc2\x11+\x01V\x90v\xc1\x9aCu\xb4\xd5\xd4\xba\x1b\xc9\x1bf\xc7\xb0\x8bq}Vn\x02i\x17\xf8:\x15\xc2\xe6\xa8M<#c\xf5\x97\x15\xd3'\x0d\xf8\x87:fR\x88\xba+\xe4\xcd$\xa9/n\xfe\x1fH]\xe76Cr@k\"\xdb\x1b\x8aN\x19(\xba\x92kN\x8b\x0c\x89PE\xf8(\x912gE\x85N\x11]\xdeuz0QM\x9f5)R7\xe7\xa0\xacs\x7f\x99\x0b!\x00&\x16\xab\x04\x0c2d\xce\x06p\xcc5Gr\x8e\x95\x08\x084Br\x9c\xe4\x88\x13\x96\xd8\xbe\xe89\xa2Xy\x01\xac>\xfa\x16\xa3\xb3\x17\xb6,G\nrT\xfb\xfc.\xd2\x82 \xe4AQ \xe4%\xe6\x05\x08w\xab\xf5\xe5\xb8\xa5~\x02+r'\xeb\xd2\xb4\x9eRb%\x97\xad\xef\xbe\x84\xba\xed\x8ar\xc2\xdfAv\x168}\xa0\x1d\xf7\xc0\x10e\xb5\xf0.tLZ \xcb\xfd4\xbb\x995\xe2\xcdH\x8e\x99z \x17\xaa\xda\xb5O`\x9eWW\xbd76\xd9\xb7Z\xf5\x86\x19:\x833\xf2o k\xe6r\xd5\x06\xc8\x07\x8a\x80\x15\xad\x08\x082\xb6l\xc8Yl\x7f\xf9\x9d\xf5\x01^\x7fM\xa9\xeb\xe4\x89}\x1es\xfd\xb5oJo\x05\x97Dx\xd4=\x16\xee\x89^G\xec\xbad^\x1f9\xb6m@\xea\xb2a(\xa6\xa7\xdcT\xb3/\xc4e\xc3\xc8\xd0\xda\xa6\xa8*\xe4\xee\x91w\xa5\xe7\x95?\xe8\xb4\x82#\x94\x01a\x05V\xc2kp\xf5\x8c\x12\x049\xf9N\xef\x9e\xd3\"\xf0;\xa4\xe1V\x1b\x0e\xff\x1c\x95\xa3\xb6\xf4\xba\x18\xa9\x19\xa2\x10\xecv_\xba5\xdbo\xcf\xea\x8d\x15\xc7\x93x\x9f6\x14\xf3+\xfa\xdb\x86b\xf2\xb8\xed\x15\xed\x1f:\x8a\xf1\x99\xfc\x9e\xa3\x18\xba\xe6r\x14sg\xcc\xe5(.\x9c\xa7q\x14\xf3\x07\xe69\x1c\xc5\xdd\xf5\xdd\x97x\x80\xa3p?\xef\x0fq\x14s\xae\xc0\xff\x89\xf2\xb7\x14\x93\xda\x91\xa1\xff\x90\xa5p\xa8>\xc2R8\x06\x9e\x87\xa5\xb8\xf3\xb0\xbb\x1d\xc5\xfd\xca\x1f\xe7(z\xd9\xa7v\x14W\x0e\xc1'\xddz\xb6'\x0c\xc5\xdc\xdf\xc7YC\xe1x\x89\xe6\x0d\xc5|\x03~\xceP\xfc \x00\x00\xff\xffPK\x07\x08\xbb@2m\xf9\x02\x00\x00\x1f\x13\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x88!+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00 \x00themes/solarized.tomlUT\x05\x00\x01!\xf9Z_\xd4W\xc9n\xe38\x10\xbd\xfb+\x0c\xcfu4\xb0,[\xcba\x0e\x93\xe9\xee\x9f\x08r(\x91%\x9b\x08\xb5\x80\xa2\x92v\x7f}\x83\xa4E)\xdae F\xfa\xfa\x8a\xaf6\x96\x8aO\x9b*\xabJ\xa4\xdb\x7f\xb7\xbb\xbf\x82\x08]\x17v\x9b\xcds\x99s\x10\xec\x17\xd2\x97\xcdv\x1bC\x89\xfb\xbd>\xb1\xdf\x1fb\xcf\xdf\xd5\xa0k\xc0\xc0\xf3\x8f\x07\x0b\x1e4x\n}\x0cN\x16\xf44\xe8\x9f\x828\xf4,x\xd4`\xe8E\xc7\xa8\xf1y\xd2`\xe4\x81\x0b\xae\x05}\x0d\"bH\x1b\x9f\x81\x06\x13\x9a\xf8\xd8\xf8\x0c5H\x89w8$\x16\x8c4H\xe2c\xec6\x81@\x83O\xa70\xda\xef-\x18\x9b\x94NQ\x1b$\x1a<\x00\xb8QhA\xd3\xb2\x83\x1f\xc6\xb4\xa9\x1dM\x99$p\xc9\xd1\x82\x89I\xc9\xf3\xfc\xd0\x9c\xe4@^\x15f\xbb\xfc\x8fi\xb11V8`\xfb\xa6l\xe4\n\xd9\x80\xed\x7fe;\x0b\xc4!\xe3\x932\xa6p\xc6L\xc2\x80\xf9\x872\xe7\x02\xb2\xf3P\xd4HY\x8bJ\x14|\xc8\xfa]Y\x85\x99\x9d\x8eI\xb7\xe9\xfd\xc2\xe4\x10/P\xc6+r\x9e\xbf\x0fX\xffS\xf3GA\xbc\xea\xd1\xab\xa4\xccUY\xcf\xadc\xda\xef\xee\xef6Qut\xd7\x10\x9c$'U\xd9\xa1\x99Cm\x9a\xa9\xac\xcd+\x91#\x91\xba\xa4\xa5\x11IJ9\xcbp6\x98)\xb8Mq\x06\xab\xbb\x9d\x1b \x96\x17W'\xcd\xe9\xbap5\xc9\x01.\xef\x8a\xe6p\x88\x91\xcf6E 5\xbcJ\x08\xcc\xa4C\xa0\x90\x95\xe8\xe6Z\xf3\xcc\xe2\xd1\x04\xca\x80\xe7\xe755\x19\xc6\x1d\x1d\xa4\x98@\xd5k\xc3\xd4\x05'\x8cK\x14\x0e\xcb$\x8a\x14)\x039\xdf}\xf3E}\xa4\xbf\x01g\xdd\xc1\xea3\xeb\x1e\xdeh)f\xd5\xfa\\\x97\x85\xd2+C\xd3.\xf8\xd3\x89\xaf\x12\xc7\xe6\x7f\xf2\xd3\xb1\xe4*\xbb\xe3\xf3Q\xec\x84!\xa7\xcbc\xab5\xd8\xe1\x8e\x86\xee\xb3M25]\xdf\xea\x1b\xf0\x15_\xbe~\xce\xfa\x0e\xd6T\xaf\x9fI\xeb\x82\xc3\x15\xc5\xdd \x18\xf6}\xd1\x0b \xaf(\x1d\xceJ\xe9\x10\xe4|\xfd\xde\xecyX\x97\x86\xd7s\"\xf2\xf7\xe1,\xcc\xf1\xc19\xe8\xf2\xd7\xe5\xf0\xa1\x15\xa5\x14\x15\x91\x0b\xfb\xd0M\xe0F^<\x8a\xed\x16\x88\xfc,\xb0,\x1d\x92\xa7\x05\xc7\xde\x8a\xe9\xe7\xde\xbe\x84\x9a\xbc~\xb9YjY\xb0,C1\xbfJ[q\x17s\x9ax\xa5\x14\x08\xa9C8\xc3\xac\x9b\xe7\xf82\xbc\xb1R\x90\xe42Kj\xbd\x127^\x89 \x16\x10\x9b\xcd`y\xe2\xadW\x9e\xbe\xf4\x8f\xc5U\x86%\x99\xe4\xdd{S\x15\xb4\x9e\xba\xcd3g\xe7\x8b|\x19\x936SI}\x8e\xb4\x19\x99\xca\xa3\xa6~ m\xd3\x1f\xdf\xfb\xb5M\xd3\xcdim3>\x8a\xd3\xda\xa6\xe6}%m35S_M\xdb,\xc8uY\xa8\x87i\x9b9\x81\xf1Pm3\xa6M\xd6=\x88k\xc4\xcdH\x0e'\xeb\xe2S\xc5\xcdd\xf4\xc7\x89\x9b\xc9\x1d\xbaH\xdc\xccJ\x8b9m\xb3\xa4\x13\x8f\xd76\xb7\xf0\x7f\x8c\xb6i\xe9\x94\xa5\x94\x9eh\x18\x946\xe3\xff\xca\x13\xd2f\xf2\xe1\x9b\x926SM\xf9 Date: Fri, 11 Sep 2020 00:20:47 -0400 Subject: [PATCH 31/31] The unused color should not be scoped to themes now This is harmless but would've generated a warning at termshark start-up. --- assets/statik/statik.go | 2 +- assets/themes/dracula.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/statik/statik.go b/assets/statik/statik.go index 82a5644..96645ad 100644 --- a/assets/statik/statik.go +++ b/assets/statik/statik.go @@ -8,7 +8,7 @@ import ( func init() { - data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x90\x16+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00gen.goUT\x05\x00\x01q\xe6Z_\xd2\xd7O\xcf\xb7JO\xcdK-J,IU(.I,\xc9\xccV\xd0-.J\xb6\xd5S\xd0M\xe3\xe2*HL\xceNLOUH,.N-)\xe6\x02\x04\x00\x00\xff\xffPK\x07\x080\xf3\x8fG5\x00\x00\x00/\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x94\x16+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00 \x00statik/statik.goUT\x05\x00\x01x\xe6Z_\xbcX[[S\xc9\x12}&\xbf\"\"\n\x08\x84\xdd\xfb\xda\xed0\x8c\x0e \x10\x14\x04\xf1\x06\xcd`_1\x03\x04H\x02\xd3:\xcao?\xdf\xeaj\x1c\xf1\xfb\xceQ\xe7\xe1<\x84\x90\xbdw\xba\xabV\xadZ\xb5:\xf3\xf3\xed\xa53\xeb\xdaG\xae\xef\x06j\xe4l[\x7fh\x0fGj\xd4;\xee\xb4\x97\xb7\xda\x9b[\xbb\xed\x95\xe5\xf5\xddN\xabu\xae\xcc\xb1:r\xe9n\xab\xd5;=?\x1b\x8c\xdaS\xad\xb1\xf1\xa3\xde\xe8\xfd\xa5\xee\x98\xb3\xd3\xf9\x81:\xfepr2O\x0f\xcd\xfb\xe1xk\xba\xd5j\xf9\xcb\xbei\xf7\xfa\xbd\xd1\xd4t\xfb\xef\xd6\x98U#\xd5~\xf8k{\xfc\xf9\x86\x0cY!CV\xca\xc0J\x19\xb2L\x86\x8c\xdf~\x17\x99\x0c\xac\x9e\xd9NW\x7f\xe6U\xe3}\x0c\x7f\x8e\\\xbfst\xf6rW\x86\xacJ7\xd9\x85\x0c\xae\xde;\x94\xc1\xe62\xd8fK\x06\xe3e\xd0M\x17\xff\xd9\x8d\xb9\xee\xec\xfa\xcb\xa9\xce\xfa\xac\x0cF\xc8`\xcc+\x19l6\xd7\xe9\xca\xa0k\x19l\xf5\x02\x9f\x9f\xc9\xe0\n\x19\\\xfe`\xed\xa9\x0c\xc6m>\xddz\xb96\xdb\xd9\x9c\x9b\xc6\xea2d9ew\x13\x93\xf7x\xc5\xbc\x1b\xe4\x88K\x85\x0c\xdc\xafV_\x07?\xff\xf5\x87\x1fA\x8952\xb0\xea\xdf\xa0\xc4\x8a/(\x8d\xde\xbbS7\x9c\xb7\x03e.OTgtvzr\x1b2\x19\x94G\xc2@\xcd\x98\xd7\xc0I\x06\x91\xcb\xe0\xd4\xac\x0c,\x93\xc1Z\x19\xbc\xa68\x05`B\xbe\xb5\x0cF\xcb`\x11?\xa7/q/\x03\xd7xv\xe2\x85\x0c\x1a\x10r\xd4\xfa\x89\x0cJ\xdc\xf9m\xe5\x1d\xc1\xe7\x15}]7\x04\xa5@\x08|B\x06\xce_\xfc%\x8362h\xb5\x80m'e\xe0M_\x06\xa6xgW\x06e\x1f\xce\xca \x1a\x19\xb4\xa7H\x94&\xf4u.\x03\xb32(\x87\xaa\xcb\xa0\xb3\xbdY\x19\xac\xeb\"\xc1\xe5\x8bp\x98\xbe\x97U#\x19<\xa7\x18\x94B\xda\x1720'\x83)\xee\x1f\xc8 \xac\x0c&\x93A\x03\x9d\xba\x91\xc1\x8a\xfb\xc8\xb7?\x87,\x8f\xcfe\x10\xba\x8f\xd4&\x880\x0e\xb9\xf1b]\x06\xa1\xac\x0cN\xc8`\x95\x0c<\xdf\x97!\xb3\xe0x\x8f\xf2c5x\xa8\xf2\xc7\xc4L\xefA!-\x83\xaa\x91\x9d\x0c&\xc1\x08\xde\x89\xfc\x0d@/\xda@\xb5\xb7\xb9\xfa\xb1\xff\x08\xebO\xack\x19\x0c\xa3r\xc4\"\xd7\x1f\x1e\xd2r\xb8\xca\xb3c\xd9\x97\xc1\x0b\xe2\x0f\xab=\xa5\x83\xe2\xa2p\x80\xc8\xe4\x88m\x19!\x7f\x90\x81\xb3_\xf6\xf6\xc0\xf7rV\x06n\xa9b\xb1\x965\xa5a\xd9\xee\xae\x0c\xce\xd5\xa3\xa3\xc7\x0fQ\xa3\xa3_\xf4\x9e\x0c\xa6\xc1w?\xc9\xe0K\x19,\xc7\n\x148\xf8j\x8bw\xa0\xdf\x0ej\xbf,\x83.\xef\x8b\xdfepv\x99\x1e\xd7\xa8}\xaa\xbbe\x04RD\xadz\x8d\xaaa\xf1=\xc4H\x14V\xe5g\xba\xca\xd8s\x19\x9c?\xa8d\xd0\xf8\x86{%\x03\x17\xc4\x01\xad\x99\x0c\xce\x18J/\xbe\xab\x956\x1eG\x87\x1d\xa2)QZ\x0ej\xa0\xd0\xe5\xdd\x01\xfe\xfb$X\xd1k/RX\x82OO\xa3\xb62h\x07\xc66\xf4A\x95D5\x9d\x18\xd3\xf8 \x19|\x05`\xf7 2\xa8\x08c\xa7\xe0.\x1a\xa63u\xdf\xd1\xd7\x04\xefB:p\xdb\x0e&vR3\x83\xe9\xc0\x1f\xd4C\x8bE\xc4ULj\x9c\xc8\x00\xf6C<|\xf9\x1e\xcf\xa2\xf0w\x8ew\xb0f\x0f\x0d\xb6\x0d\xa8d\x10\xa5\x0c\x8e\xe5\x93\xbfG\x01\x91\x94\x19\x18\xc8\xf2\x8b)$\xf0\xb7\x0cL\xb7\x01\x1e\x88e\xee\x91\xd0\xd8:\xc7>6\xeb\xde\x03\x95\xf3\xbb2d\xfeM,\x89\x06\xaa\x14\xa1\xb1\x0fd`B\x06\x9f\x81;\x1e\x051\x94*\xd0\xd1\x85#\xe9b9!\x83\x96\x17\xe5E\xac\xc88 \xac\x96\xb7Ai\xf1\x08\xa8\xed\x9f\xca\x90\x01\xf7\xe6\x1ea\xc7\n>CeT\x06r\x0b\xa9\xaa\xbb2\xf0\x8a\xfaN\xab3T=>\x8e\xa4\xd1\xcf\xc8\x17[zG\x85\xe4h\xdf\x82\xda\x06\x18sF\x92\"\x0c\x9e\xdb\xd8\xbd\xa9\xd2\x91\x0c\x8a\x1d\xed\xc8\xc0\x13\xb4\x99\xa1\x1d\x9d!\x82(3C\xe0!CWV\xb8;\x0bl\x97 h\x0d\x81\xa9\xc7\x16\x00\x06\xc9\x99K\x0c6\xf5=\xa8\xd3+\x12!\xeb\xb3\x0d\x19\\s\x0e\x9e\xb39\xa4_`\xc7\xc3\xa4%\x95\x0chy\x0e\xcc\\\x8e}e\x10\xc0\x90=\x06M(\x18l\xe6b\xbd\xae\x12\xe7\x0c5#\x9a\x9ag\xef \x7f\xb0\xc4\x96\xcfhAS.\x1e\x0e\xd3f\xd5|7%U>_\xa6Ii\xcb\xcfs\x0b$\xe2\xbcxBikH\xb5e\xf4!\xab\xae\x0e\x96A\xb2$\x87\xd5\x94\x0c\xaa9\x93A\x0b\x8864\x04\xa2\xce\x97f \x01\xc6\xdf\xee%\x89E\xf5\x15\xd6d\xd4$1\x1cG\xfd\x84\x06\xf6\xf9*J\xb0 U\xe2\xe5\x03\x10O\x185\x87H:\xa4\xd4\x9cm\xe3\x0f\x94\xbf!\x15\xb1\xfao\x88\xea\x884\x0c@euo!q\xbe\xbe\xbbHrf\xa26\xed\x7f\xfc\x03e\xcb\xa9\xa8FLP\x18\x16\xc86\xd74\x91@i\x0bJ\x16\x01\x12u\x82\x16&\xf7\x01\x9c@\x04^\xeb\xeb\x15\xbal\xf5\xf3\xa7I'\xf4+\xba\x0b\xbe)F\\CA\x84\xa3>p\xb1\"h\\v\xbc\xb8\xca\x1a\xb3\xb8\n\xe7\x91/\x90*\xe0q\x01\x11\xf6\xf4\xee\x9as\x880\x92l\xfe\xdaO\x891\xda\xcb\xe4i\x84\x99\xdfP\xcem\x920\xc0\x1fA\xcc&\x1f \xa7\x9d\xa7\x7fN\x92\xdcy\xb7D\xff\xe0\x8b\xd8\x08\xc2\x94\xf1\x8dT\x08Au\xcarX\x9c\x8a\xc2\x89\xe8\x02\x0d?\xd5\x7f\x8d\x15\xf8*\xda\x9c/\x129U\xc1\x87X\xe2\x0e\xf5\x92\x89=\xfe\xab\x9aJ\xd3\xda-\xd25\xa6PI\xe37\xca\xb3t\x8b\xa19\xb2!u\x97\xcb\xb7n{\x90o]\xd1\x1b\xb7G\xa3\xf6F\xff\xbf\x98\x15\x7fcX\xfe\x0f\xc6\xa8\xfa\xd6\x18\x0d\xcfN\xd4\xa0\xf7\xd1\xd9\xefX#[\xc2\x1a\x89>>sj)\x1d}\xd1L\"\xa0\xbf\xc4\xb4\xc8f\xf7a\x8c\x14\x89\x1eZ\x1c\x98\x82\x87h\xa4\x8c\x0f \xcf\xec\x1e\xcd\x17\xe4\x83\n\xc01F\xad\xcf\xaf@\xc5\xcf\xe8\x11\xb4\xf1\xca4\x06\xb1k?I\xa6I%\xe5\x83\xd4!\x13\xae\xb7\xb0\xd0\x03hP\x97\xe6\x0e\xd8\xdex2W\xd0\x13\xb4\x04\x88\x94\xf1\x03\xe0vE\x1b\x1b;\x94A\x88!\xe5\x01!\x88\xb3;'\x97d\xec\x15\x8a\xbbD3\x16\xceO[p\x85\x91\xf5c\xb8\x02;\x83\x0f\xe8L\x95\x1d\x93m2_\x9co*:L\xa3+\x83\xec\x7f\x06H\x9b\xb0%\x10\x8829\xc3\x18\x1bt\xae\x9c\x0d@\x18\x06\x91\xaf\x90\xc3P\xb8\xce> \xaaw$\x8b\x10\xf9\xe8\x990\x7f\xaa\xcf\xd1<\x8c\xeb5\xe2a\\\x8b\x91\xea\x81LP4\x18=\xf8\x13\xbcg\xa6ZC>\x7f\xc1h\"\nn\xca5\xb0\x16\xba\x0f\x8d\xe5\xec\x11`\xdf\x82\xfee\xe4\x7f\x9c\x9f#C$\xe2\x84\xdb\xdc\xc6V\xf8:\xdc)\x18\xa2\xf9\xf6\xfb\xc7\xa4\x14\x90C\x14\x0dd\xc6r\xba\x84~b\xfa?\x03\xd2\x13\xe7$\x1c\xd0]\x94\x83\x8bu\xba\x10g\xbd!\xf5\x13q\x06=\xfa\x03\xc5M\xe3\x00\xb7\xb8\xef\x01|\xf6\x8a\xbf\xa3j\xa8\xfa$\"\xd2\x87?%\xf68\x8b\xb2\xbb_\x08!S\xdeIV\xb2\x80H\xd4\xd8\xbc~\x9a\xfc_\x96\xe6I\x83Q\x94Z\x11\xd1\xfb\xe29j\xb0\xf6\x16\xbc\xea\xa2\xf9>A$@\xbb\x83\xb74\x9e\x85 O\xc9\xdd:1\x0c\xdc\xf6\x96\x16\x8e\x93+\x9b\x7f\x8e\xbdf\x06\xa4\x95`_\xe6\xdf@\x08^`\x87\xd5\xc74\x86\x9d\"(T2Y\xc6\xbc|K\x9cR\xd0o\x02\x9f\xda\xc6\xf9z\xfbrD3\x87E\x90&&_R\xe2Pf!\x96N\xd3\xfc\x124V\x8d\x9dA\x8b\xdd%\xe3\x8dN\xc4\x0e\n\xce\x83\xadw!p\xfa\xbc\x8e\x96\x0f\x05|vA\x9cA$\x00\x1c\xe91=F\x12\xcb\x9a\xd7\x93i\x90\xc0\xfc\xa8\xf3\x8a\x8a\x96\xb1\x0e\x05\x88nt\x80\x17\xf6\x0f\xdb\x19_\xaf\xb4\xe1\x1bL\x97\xda\x1bb\xae\xca%4 \x0d\x1eQ%\x1fQ\xefQ\xf5\xf1\x84\xbb\xb1\xc3%M\xb7XR\xe0\xdbT\x15y1p\n'\x9cx\xca\x81,\xe6k\x10xE\xe7\x10\x04\x0b\x11\x16,\xebls\x1cv\x8a\xa9\x9d\xf4`$\xd5\xa7dZ,M\x04\xaf\xd6H&\xa0\xb4Q\xf0\x15\xd1\xd7VG\xb0\x89\xbfR\x99,\xdbI\x19(rTZtR\x0f\xc2\xafcGU\x1f\xd0\xc4Q\xa8Oqx/M5K\x03\x00\xa4\x88>\n~\xa3|\x90\x1dG\xab+\x00\xbd|\x8d%\xdfP\x81\xb3d>\xa2m\xc9\xcd\x19\x0dx_\x0e\xcf\xa8\x96\xbc$\x80\xb4\xb9F\xa5\xde\xfe\xf96\xce\xa3\xa5\xe8\xcf\xba\xfb8\xe86\x90}\x15R;\x94DQ%\x968\xe5\xa3\x12\x1eQ\x1f\xed<]dq\xc2U\xeb\x1d:\xae\xc4!S\xcf\\$\x81\xc1\xac\xc4\x01\x00\xc32VO\x10\xac\xa2\x9e\x84\xb1\xfeHu\x8d\x1e\xdd\x93\x9f\xc2S\xf0R\xe0\xb9\xce&hH\xea\xb4\xb3\xb5i\x00\xe30\xa3p\xf6\xbfi\x11,\xec\x85\x1c?\xa4^\x88\xaaaww\xa0\xfcwO\x9f\x90-\x86\x05U1\xa3\xc3\xdfi\x14\xc2\x8cb\x9a\xf8\xe6\xfa\x14\xc8\x8d\x86{t\x83\x1b\x8a\x08t\x80V\xa3\x8b\x94\xf8p\xb8\x8e\xdbO\x89\xc0q\xe4\xdb\x02\xd0\xeb\xc1A*n$\x97\xf2\x9b\xef\x16voV\xc2\x19\xbf\xb8\x84\xd2\xd1zL]\x14t\xbe\x03~\xf1\xdc\x14\xa7\xd1..\xc7\x83\x95\x8dn\xd1\x0f \xc3\x1d\x9c\xa4\x1a\x02\xd7\xe6z\x13\xab\xce&+V\x12\xca\xf0\x08\xc6\xce\xd18\xc0\x84Q\xc5\xe9\xdeC7\x8d:NR\xb7D\x0d\xabR\xe3\xf1\x7f\x1c9H\x81\xfc\x84\xf8D7\xd4?\xe0\xbdX\xdc\"\xad\xc5\xb2`\xd7\x7f\xb3n'\x14\x9d@\x1b\x82\xee\xd9\x17\xb7\xd6\xfd\xca\xa4\x91uc4QX:\xb1}\xef\x17\xc0\x1f\xf9\xad\xec\xd6\xaf~\xdf\x7f\xe9\x1b\xafp\xeb\xfa\xff\xf8\xb5\xf0g\xe2\xbe\xb1\x9e?\xe8fo\xfd\x14\xf7\xfd\x17\x85>\xf8\xfa\xe2O\xfc~\xf7o\xf2\xf8\xc1\xd2\xder\xce\xdf\x7f\xdd\x94\x00K\xff\xb3\xe2O9\xee\x98LuS\xfao^\xc5\xedw[\xdc\xbe\x0f\x9f\x10\x99{\xeb{\xe3\xad\xb11?\xec\xec\xb8\xa3\xdep\xe4\x06SV\x8d\xd4tk\xecsk\xec?\x01\x00\x00\xff\xffPK\x07\x08\xb0\xa8\xa2\xa7{\n\x00\x002\x17\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x81!+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00 \x00themes/dracula.tomlUT\x05\x00\x01\x12\xf9Z_\xccW\xcdr\xe2<\x10\xbc\xf3\x14\x14\xdf\xf5s*\x18\xfc\xc3a\x9f$\x95\xc3X\x1e\x81+\xf2O\xc9R\xb2\xbc\xfd\x96%#\x83e\x0b9\xb5a\xe1\xd83\xed\xd6\x8c\xa4Q\xb3\x92\x95l1_\xffZo\xfeK\x0e\xb8\xdd\xc2f\xb5z\xcb9\x10\xc9\xe0}\xb5^g\xd0\xe2\xeb\xab\x8a\x87i\x08\xbbxs\x01\xb7\n\xdc%\xbbt\xbf7`\xa8\xc0}\xbcO\xa2\xd0\x80;\x05Fq\x94\xc4[\x03\xee\x15\x98\xc5Y\x9c\x0d\x99\x91\x02I\xf7K\x0c\x18+\x10C\x0csb\xc0D\x814\xa5)\x1d\xe8\xa9\x06i\x14E\x91\x01\x0f=\x98\xa5\xf1@\x07\x0dn)\xa4\x03\x98\xe9u\xbeRH2\x03\x12\x05\xa6\x19\x1ehn@\xdd\xb08LB\x18jG]Q~\xd8\xd1\x83\x01i\xaf\x9e\x1c\x88n\x1d\x03\xf2\xd1a}\x8f_t\x83uH\xa2\x15Q\x9a\xe4\x0c\x95\x15Q\xeb>rD;\xa4V_\xc2\x11+\x01V\x90v\xc1\x9aCu\xb4\xd5\xd4\xba\x1b\xc9\x1bf\xc7\xb0\x8bq}Vn\x02i\x17\xf8:\x15\xc2\xe6\xa8M<#c\xf5\x97\x15\xd3'\x0d\xf8\x87:fR\x88\xba+\xe4\xcd$\xa9/n\xfe\x1fH]\xe76Cr@k\"\xdb\x1b\x8aN\x19(\xba\x92kN\x8b\x0c\x89PE\xf8(\x912gE\x85N\x11]\xdeuz0QM\x9f5)R7\xe7\xa0\xacs\x7f\x99\x0b!\x00&\x16\xab\x04\x0c2d\xce\x06p\xcc5Gr\x8e\x95\x08\x084Br\x9c\xe4\x88\x13\x96\xd8\xbe\xe89\xa2Xy\x01\xac>\xfa\x16\xa3\xb3\x17\xb6,G\nrT\xfb\xfc.\xd2\x82 \xe4AQ \xe4%\xe6\x05\x08w\xab\xf5\xe5\xb8\xa5~\x02+r'\xeb\xd2\xb4\x9eRb%\x97\xad\xef\xbe\x84\xba\xed\x8ar\xc2\xdfAv\x168}\xa0\x1d\xf7\xc0\x10e\xb5\xf0.tLZ \xcb\xfd4\xbb\x995\xe2\xcdH\x8e\x99z \x17\xaa\xda\xb5O`\x9eWW\xbd76\xd9\xb7Z\xf5\x86\x19:\x833\xf2o k\xe6r\xd5\x06\xc8\x07\x8a\x80\x15\xad\x08\x082\xb6l\xc8Yl\x7f\xf9\x9d\xf5\x01^\x7fM\xa9\xeb\xe4\x89}\x1es\xfd\xb5oJo\x05\x97Dx\xd4=\x16\xee\x89^G\xec\xbad^\x1f9\xb6m@\xea\xb2a(\xa6\xa7\xdcT\xb3/\xc4e\xc3\xc8\xd0\xda\xa6\xa8*\xe4\xee\x91w\xa5\xe7\x95?\xe8\xb4\x82#\x94\x01a\x05V\xc2kp\xf5\x8c\x12\x049\xf9N\xef\x9e\xd3\"\xf0;\xa4\xe1V\x1b\x0e\xff\x1c\x95\xa3\xb6\xf4\xba\x18\xa9\x19\xa2\x10\xecv_\xba5\xdbo\xcf\xea\x8d\x15\xc7\x93x\x9f6\x14\xf3+\xfa\xdb\x86b\xf2\xb8\xed\x15\xed\x1f:\x8a\xf1\x99\xfc\x9e\xa3\x18\xba\xe6r\x14sg\xcc\xe5(.\x9c\xa7q\x14\xf3\x07\xe69\x1c\xc5\xdd\xf5\xdd\x97x\x80\xa3p?\xef\x0fq\x14s\xae\xc0\xff\x89\xf2\xb7\x14\x93\xda\x91\xa1\xff\x90\xa5p\xa8>\xc2R8\x06\x9e\x87\xa5\xb8\xf3\xb0\xbb\x1d\xc5\xfd\xca\x1f\xe7(z\xd9\xa7v\x14W\x0e\xc1'\xddz\xb6'\x0c\xc5\xdc\xdf\xc7YC\xe1x\x89\xe6\x0d\xc5|\x03~\xceP\xfc \x00\x00\xff\xffPK\x07\x08\xbb@2m\xf9\x02\x00\x00\x1f\x13\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x88!+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00 \x00themes/solarized.tomlUT\x05\x00\x01!\xf9Z_\xd4W\xc9n\xe38\x10\xbd\xfb+\x0c\xcfu4\xb0,[\xcba\x0e\x93\xe9\xee\x9f\x08r(\x91%\x9b\x08\xb5\x80\xa2\x92v\x7f}\x83\xa4E)\xdae F\xfa\xfa\x8a\xaf6\x96\x8aO\x9b*\xabJ\xa4\xdb\x7f\xb7\xbb\xbf\x82\x08]\x17v\x9b\xcds\x99s\x10\xec\x17\xd2\x97\xcdv\x1bC\x89\xfb\xbd>\xb1\xdf\x1fb\xcf\xdf\xd5\xa0k\xc0\xc0\xf3\x8f\x07\x0b\x1e4x\n}\x0cN\x16\xf44\xe8\x9f\x828\xf4,x\xd4`\xe8E\xc7\xa8\xf1y\xd2`\xe4\x81\x0b\xae\x05}\x0d\"bH\x1b\x9f\x81\x06\x13\x9a\xf8\xd8\xf8\x0c5H\x89w8$\x16\x8c4H\xe2c\xec6\x81@\x83O\xa70\xda\xef-\x18\x9b\x94NQ\x1b$\x1a<\x00\xb8QhA\xd3\xb2\x83\x1f\xc6\xb4\xa9\x1dM\x99$p\xc9\xd1\x82\x89I\xc9\xf3\xfc\xd0\x9c\xe4@^\x15f\xbb\xfc\x8fi\xb11V8`\xfb\xa6l\xe4\n\xd9\x80\xed\x7fe;\x0b\xc4!\xe3\x932\xa6p\xc6L\xc2\x80\xf9\x872\xe7\x02\xb2\xf3P\xd4HY\x8bJ\x14|\xc8\xfa]Y\x85\x99\x9d\x8eI\xb7\xe9\xfd\xc2\xe4\x10/P\xc6+r\x9e\xbf\x0fX\xffS\xf3GA\xbc\xea\xd1\xab\xa4\xccUY\xcf\xadc\xda\xef\xee\xef6Qut\xd7\x10\x9c$'U\xd9\xa1\x99Cm\x9a\xa9\xac\xcd+\x91#\x91\xba\xa4\xa5\x11IJ9\xcbp6\x98)\xb8Mq\x06\xab\xbb\x9d\x1b \x96\x17W'\xcd\xe9\xbap5\xc9\x01.\xef\x8a\xe6p\x88\x91\xcf6E 5\xbcJ\x08\xcc\xa4C\xa0\x90\x95\xe8\xe6Z\xf3\xcc\xe2\xd1\x04\xca\x80\xe7\xe755\x19\xc6\x1d\x1d\xa4\x98@\xd5k\xc3\xd4\x05'\x8cK\x14\x0e\xcb$\x8a\x14)\x039\xdf}\xf3E}\xa4\xbf\x01g\xdd\xc1\xea3\xeb\x1e\xdeh)f\xd5\xfa\\\x97\x85\xd2+C\xd3.\xf8\xd3\x89\xaf\x12\xc7\xe6\x7f\xf2\xd3\xb1\xe4*\xbb\xe3\xf3Q\xec\x84!\xa7\xcbc\xab5\xd8\xe1\x8e\x86\xee\xb3M25]\xdf\xea\x1b\xf0\x15_\xbe~\xce\xfa\x0e\xd6T\xaf\x9fI\xeb\x82\xc3\x15\xc5\xdd \x18\xf6}\xd1\x0b \xaf(\x1d\xceJ\xe9\x10\xe4|\xfd\xde\xecyX\x97\x86\xd7s\"\xf2\xf7\xe1,\xcc\xf1\xc19\xe8\xf2\xd7\xe5\xf0\xa1\x15\xa5\x14\x15\x91\x0b\xfb\xd0M\xe0F^<\x8a\xed\x16\x88\xfc,\xb0,\x1d\x92\xa7\x05\xc7\xde\x8a\xe9\xe7\xde\xbe\x84\x9a\xbc~\xb9YjY\xb0,C1\xbfJ[q\x17s\x9ax\xa5\x14\x08\xa9C8\xc3\xac\x9b\xe7\xf82\xbc\xb1R\x90\xe42Kj\xbd\x127^\x89 \x16\x10\x9b\xcd`y\xe2\xadW\x9e\xbe\xf4\x8f\xc5U\x86%\x99\xe4\xdd{S\x15\xb4\x9e\xba\xcd3g\xe7\x8b|\x19\x936SI}\x8e\xb4\x19\x99\xca\xa3\xa6~ m\xd3\x1f\xdf\xfb\xb5M\xd3\xcdim3>\x8a\xd3\xda\xa6\xe6}%m35S_M\xdb,\xc8uY\xa8\x87i\x9b9\x81\xf1Pm3\xa6M\xd6=\x88k\xc4\xcdH\x0e'\xeb\xe2S\xc5\xcdd\xf4\xc7\x89\x9b\xc9\x1d\xbaH\xdc\xccJ\x8b9m\xb3\xa4\x13\x8f\xd76\xb7\xf0\x7f\x8c\xb6i\xe9\x94\xa5\x94\x9eh\x18\x946\xe3\xff\xca\x13\xd2f\xf2\xe1\x9b\x926SM\xf9>\xf4a\xa1\x97\xc0\xbfC\xca\xc4\xbf4\xd1\xeb}\x18m\x14G;A\x98\xc3\xc1j>\xb1M\x8e\xe9m\xb63|\xb4n\xf9\x85H\x08 \xfc_\xd58J\x1b\xf6\xc5\xd5D\x0b\xbe<\x9d\x82k|\x83(X\xd6\xb1l5\xb4\x03g\x86\xf46m\xeapc\xa2\xcaL\xaca\xe4*\x9a\xe8\x0b\x04\x1c\xbbK\xc8M\xd4\xfa\xd5\x9dB\x1a(\xa5\xdd\xde\x03\x9a<\x17p)L\xd4\x95\x89\xaa\x9d1Q\xd7Ej\xa2K\xb08\x87\x91ML\xf4X\xc6N\x0c\xb3j\xe0j\xcemV,\x0c\xdc;\xc9\xc2\xf8\xda\x9b\x83\xab/\xdfz\xd87e3\xd7\xf9\xbc\x89>\x9df?\x833B7>Yd\xdd\xd7\x8a\xb5\x1a\x128U\xa7\xd11>\x89W\xbb\xe2+\x10\xb8\x04f\x15\xfb\x7f\xc2%\x11\x9e\x9d\xea\xdd\xbe\x89J=7\xd1V\x13\x9f'\x18\x0e\x9a*~\x01\xbc\xde\x9dYY`H\x8fw\x81\xed4\xe9x\x89\xb5\xd1\xd6\xa7\x1c\x8a\xaa\x11\x17\x81.\x8a\xdb\x0f\x15#\x8e-\xa6\x01R?\xfe\x14\xe0 {\xf0\xf0!\x13uV\xa6\x87,\x8dNN\x1f\xc1W\x92\x95=~\xca\x96\xf1\xe0-\xfe\x1b\xa0W\xf6\x1ao\xfbE\x96\x9a\xc1\xc1V\x0bP\xd2C\x13\xdb\xfcz\x9c\xa5wY:\xcc\xe6\xb0\x9d\xd3%\x1c\xc4\xaey\xc7_\x85\x9e\x823\xf7EZl\xef.$\xd8\x11\xf4\x15\xc2Q\xd5O\xb1\x81\x89Jc\x8f?\x19\x82T\x95&\x8f \xf9\x08;7\x89\xa6\xfb;\xdd?\x84\xeegX\x90\xbaa@ +)\x98_@\x14\xdb\xc0ut5\x86,\x84\x1c\x18\x18\xa3\xf18\xdehp$\x8dD\x95\x04\x0e\xa4V\xb9\x0f\xa2\x18\xd5m\xb6*\xa1S\x93\x95\x9e\xb2\xb3\xd5\xd5\x00\x9f6$\x91\x17kS_D\x13-\xa0-5\xd1\xc3\x83\xf2e\xa0\xcf\xcc\xc0\x1f8\nk\nbBd:\x04\xe4EZ\x81\xa3\xc2\xd6\x00\xa4\xa4\x99\x9e\x80\x12\xa6\xd9b\xf0Z\x92\xcc\n\x8c\xb6\xfc\x95\xe0\xd6 \xe01\xab\x9a $\xebQ\x00\xdcg\xf7\x85\xca\x14@#\xed\x99\xe84\xc7\xb9\xcb\xbcDg%\xbb;vA\x80\xacu\xcf\x18\x92u\xc6o\xda\xf4\x02.\xc3\xba\xe4\x08=\xc5~\x03\x8cmHJZaQxZ\xc9/\x8f\xb4_\xf5\xb6\xa1Ed\x05@\x00\xe4\xae\xcb\x02\x8b[\xde\x1e6\x05\xe4\x11\x80@\xc8\x82\x93\x03^\xd0\\\xd2\xaewo`\xed\x9c\x81\x96N\x89=\xd4\xcd\xf3\x0e\x99^A\xa01\xfa\x160*\x87\xfa\xf5'\xb8\xda\x0d\xbb$\xac\x95\x84O\x00a\xf8A6\xce A\x03\xa6\xdap\xf1\x85\xf1\x9c4\x05g\xd6\xb7l'\x07\x17M\xf2\x8d\x17\xc8`\xdf\x18O,>h\xd53\xa8y\x01\x9bm\xc3\xad\x0f\xd8\xa1\xa0\xdeF_\n\xa2x\xc4:a\x10\xf4\x01\xe6P~\x1fb\x87U\x15\xe3T\xa289v\xe9\x04zPzi\x82\xa3\x03\xe9\xb4.JH6/\xb0\x07 \xd2\x91\xf5]9\x0f\x1d.\xe1\xc0\x02'\xc1z\xf0\xf0\xd6\x0d\xee \x03\x1eOl\xec\x98\xa8\xd3\xb55V\xa5#\x14\x007\xf3%\x8c\x95\xcf`\x87\x17\x05\xfb#\xc2\x8f\xd2\xaf\xee\xc3\x11~\x10\x8e\xc1\x94});u\x82\x1c\x88@\x0d\xc98\xc3\x83-\xb3k\xd6/\xa5[\x81\xa1\xd0\xfe9y\xc1i \x0f\xbfld\xe2O5\xfb\x86\xa2\xb8\x9b\x85\x01\xe6\xa0\x87\xad\xe5O\x0c\xcf^\xe8^[\xce>\xfd\xc1y\x19F\x01q\xb5\xa9\x89mr\x83l\n\n\x04_\xa5W\xc3|\x15?I\xd7\x88m+X\xaa\x03k\x06\x18\xed\xdayV\x0f\x05_\xce&\xc2\x81\xbd\x04\xabv#\x83\xd8B\x92\x84_\x96\xff(\x96A\xd3b\x81\xbf\x0f\x957\x1a\x04\xae\x9a*\x85l\x05\x81\xa2\x82\x99\x98s)\xf8JX\x19\x13\x08/\xd9\xa7\xc0\x0d\x81+\x88\x87\xa4m\xf6\x05\xa9\x91f\xb5z\xc2{%\x05\xc9\xf8X6\x02\xa0\x15\x0f\xa0>\x87\xb0\x83&\x91d\xe1C\xa1\x98c\x88\xf2\xbaCF|\xe4\xaa\x99#\xa1\x9f\x9a-G4\xaee\x0f\xad\x13q\x94\x8aI\xbcB\xf6D\xecL\xcd\x06P\xe2\xb2\xc7\x98\x80\xec\xcea\x90\xb2b\x08V-\xf1\xbf\xec\xcd\xb3)V\xb0\xcdYD\xdf\xb2.U\xf5\xfe\xdb<\xe7\xb3\x90\x1f\xb2o\xe8\x94!\x1d'U\xfa\xf2\xa5\x89u\n\x8cJ:\xf6\x04\x05j\xa6m\xd8\x15%\x0b\xf4LF\xb4\xec\xf68\x01\xd6'\x07\xca\x84\xb1\xd6\xacL|^7\xd0M:\x9c\xcd\xc2=\x07f\xdfn\x03\xf2\x9e\xe0('\xc0\xba\xbeC\xf6\x12\xa0\x15q#\x84SyWJ\x01J\xea\x1aT\x0bl\xa5-\x9e\xb1\xe2\x9a\x0cd)\x1d`+\x03\xed\xb1\x08x\xd0%\xa7\x0c\xc8\xef2\x08\x92\xef\xed\x0b\xe6\x03\xb4\xf2\xa1.]\x1c~\xe5'\xbd\x07y\xd2\xe1VR\x90\xa7\xf2\xef\x19\xb6_\x19\x86bN`\x0f'j\xc0C)'\xf8\xc4\xc3\xfc5Lg\x0f\x10]}\x0c\xbeN\xd8\x0eL\xee\xdb\xc3\x99\xcdq\xf1\x1f\xa84C\x90\x15?\xaa\xa1\xad\xa9=\xc4\xe9\xe2\x11\xa7h\xdfBp7\xc8\xf0\xee|\xfc\xf3\xe8x\x01y\x06\xb0\x91\x0f0\xeaXY\xa5.g\xc0\x80\x07\x19}\x80\xd0\x08\x1d\xc2`\xe8\x90\x130\x98\xbe.\xa5\xe6A\x9cz\xca\xec\x90\x12n\x06uR\x89\x13\xc0C\xea9&q\n\x88U\x02\x1c\xf3AX\xd0\xcfs9\x80J'\x91<\xd2\xd6\x8fQ\x8e&}[,,b\x99\xd03Y\x83\xba\x88\n.L\x8a\x0bB\x0f\xfe\x94\xebU \x03\x04u\xed\x00\xefDY\xd3\x9eCP&\x8b\x9a\xf6\xf1\xd3\xe1\xfa|\xe0\xb305b\x8a\x0b=\x02\x9b\x92\x1d\x0eZ\xa6\x84\x0c\xea\xdd\xb1j\xab_.\xbff\xf0\x80\x03yMD\x1a\xfb\xab\xe9#\xe8cI<\x0b\xe7J\xaf\xd9\xfdU\xf9\x90#\x8e0\xc8\x83\xa6\xe5\x1f\xd9\xa9\xf0\"!\xadpo\xe0\x87\xad\x16\x1f\xc1 \xa3\x9cY\x12\xaapF^\x1a3;\xc3J\xb2\xd9\xd4\x05\x1d\xec\xfe\xa11_\x98{v)^Y\xa1a\x1a \x11.T\xab 8\xdbc\xc4\xc6g\x11M\x89\x96s\x016{)L\xa8NO8\x8d4v\x0d&S\x1c\xbd\xc4\x7f\xf3#\xa9F\x11\xa6\xedk\x18\xfb\x14\xdfH:h\x9f\x81\xa3?\xc4\xff\xfa9W \xb3\xe0g\xd5\xaeR\xdc\xc7\xc6\x7f\x05D\xbd`\"\x03F\xad%\xfe\x81V>e\xdb\x01\xbdt\x03<\xd23\xd78Vo\x87\x93\x04\x00\x01\x0f\x01UQDR\x0f\xa2y\xce\xeeI\xf5V\xc3\xfal\xf5c)f\xfd\x04,Qo ;\xacv\x84\xf0\x04\xd6wM\xab=\xe33S\xd2\xb5K\x9c \x1b\xfb\x07\xd0\x08\xcc\xa4|\xbd\x08\xe7\xaa:\x12u\xc5b[\xae\x11\x07\xae\x85O\xc1N\xd5\xd9w(\xfb\xfc\x03#\xa3\xa2\xf7\xe69\x03\xb7\xe5K>\xb7k\x99\x06+\x92\xda\xef\xf0\xeeT\xe8\x96B!-\xd7\xacp4\x80\x8e\x87\x02\xa9\xf8H\xd8\x95(\xe5B\x0b\xed\xce\x83\xf7\xac\x14E\x15\xc3\xd4\xd4\xe5\xa7\x11\xc0\xe2-\x1eZ\x06\xd4\x123o\x19/\x00\x9d\x9e\xf8je\xb9\xaeEV\xd2\x94mw\xb9\x0d\x01\xc9\xe0\xb6\xba:\xe4\xf8\xa2\x06\x07L\n\x9cG\x9dI\x89\xb9\x914\x94SZ:\x83\x0f\xa8\x97Li(i\xb7\xd3\\\xe2\xd1\x974\xfb\x9c\x13\xe3z+\xa1\xee\xd4\xf1\xf3E\x82\xa8!)\x11\xa0y@\x11p\x1ePD\x04X\xa8N\x92\x1d\xbef]S\x03*\xb0\xe74!\xa6o\xf9\x81.9$9\x87{\xe7L 1\xf5/sl\x10\xe5\xa8\xd0F0\xe9\x01\x81\x165\xc0\xdfp\xd9\x93G\xd8\xa2\x91\x93\x16\xect\xce\xbd\xc8$\xe4\xc3\xda\xfb\x0b\xceq\x08\x8e\xb6\x90\xd8K\xd2',}B\xe7]\xe6\x0c@\xd9);M\xf7\xe0\x89\xe7\x8f'\x97\xe1 \xa3B\x89\xa4G\x90\x94\x0b\\*\xbbd\x88\xb3\x83\xd6i\xcd\xa9\x84\x80\xc6]\xb1*j.\x0f\xc4+\xa42\xb5\xc2\xc4}\xf6Z@ L!\xfa\xec\xcd {)\x16l%^j<\x89=\xa9h#\xe6\xf6\x94\xed\x1bPl\x84\xd4R\x9ar\xbc4\x81\xa5Z~\x0d\xc6\xa4\x9f1\x8f%\x0d\xfb\x03\x98lwv\x8a\x17\xf3\xea\xd0\xdc7gp\x01\xd0/\x97\xb0\xa6\x08\xbb$&\xa1c\xd2\xa7\xda\xbf\xa3\x05-\xf5-r\xa9&\xe0y\xed\x83YIN\xd4is\\\xf9\xfar\x1b\x9bO\xe2\xb7\x11>=Q\xc2Z@\xccq\xea\xb5\xd2D\xac\x93Y\xe9\xc3\x81\x16{P\x1a\xaa\xf8\xf2\x9f\x0e\x81\xf5\\\xca\xef\x94\x0f\x13\xaaAv\xfb&\xf9\xc0\x0e\x03\xdbhw\xcd\xdfR \xf8=\xf8\x05e\xae\x0c\x00Z>\xe3\x00\xc52\xbe\x90>i\"\xec\x11&\xa0\xa6\x9b\xfa\xc1\xe2\x91\xfb@\xe5\xd9\xa7.\xb6\x07\xa9H\xa3\x10{>\x84\xa74+\x1dd\xcb9\x94\xe6z`\x13\x8e\xbd\xb8\xcdo\xead\x81\xc3\x9f\x98\x92\xff\xc9\x8c\xa0\xf5{L\x00B\xb9\x08\xb1\xd7!\xff.GZ#\xbd\xc4\x90\xd6\x9f\xd8$\x90\x85\xcc\xe2\xd9\xb2\xac\xc2\xa9\x99\xc1i\xee\xf6\xe1\xd8\xae\x92RK\x17\xfdk\xcc\x8b\x9afR\xdd\xb5d\xb0H-\x0b\x81\x01\xd6pf\xf8\xae4\xbb\xa2\xa3z\xabdk\xd4\xc5\xfe\x1e\xc7j\xd3Rs\xe2\xe3\xfe\x1d\x9b\xf0\xd8\x0b\xf9\xb3n\xcc\xfd\xe17\xe9;F?\xe7\x7f\xbc\x827l\xc0\"\xe7w\xcd(\xcaBD\x0c\xbe\xe1d=\xc4\xa3\xae\xe6`\xee\x87\x0f\xf76p\xd2\xd7H\xf9u\xb9*\x95\x0c\xdc-E\x8d\xa6\x89\"\xceH\xaei\x86\xd8`\xbaY\x95B,\x10z\x05)\xd2\x9b\x9b\x81\x0b\xa9\xb7\xa5\x8e\xc0\x81\xa9\xe0(~|A\xd1\xe4g\x06\xa5\xc1\x0cU\xd6\xea\xd9K\xe6k\xde\xb1]P\n\xd5\xd9\x91\xc4e\xc2\xa2\x90\xe53v\x07\xbc\x88\xab+F\xb1\x04(\xa3\xcf\xae\xb8\xda\xa6\x0e\x9fl\x89\x10P\xf5\x1e\x879\x90-PRYf\x14\xa4V\x89\x13\xce\xe6\xedP\xde'\x145\xac\x03IPD\x07\xfcC\xc4BqP9)\xc8}6\xb4\x02\x83\xeflHg\xcb\xbd\x01\xdc}U\x17\xe6\x0c\x08\xe8#g;\xad\xc6`\xc2c\x84B\x9c\x93\xaeS\"\xad\x7f\xc7\xce\xa6J<\xe3NP\xfd\xfa/|>\xe2\x88(\x83\xf2\xfdA)\xf0\xabfwlE\xa8\"\xf5d\x17\x1eJr\xca\xd7\xb9\xca fH\xc5\xe4\xf6<{-2\x93k7\x00]c\x10r\x11\xfa\x18\xee\xbd\xd9\xbd\xda\x90\x8c\x89\xfa\xb4\xf5\xf3[R[\x12/^\x87\xbe\xd7>\xc7\x89\x95\xa2\x9b0$\x92\xcb\xf4\x949\xfb\x89h\xfc\x01\xfb<\x05/\xf1\xbb\x9bo\xd9\x15\x14!\xdf\xd0+q\xf2\xc0 \x0coi\x85o{\xc1a\xdb\x8e\xafs8B\xb1*\x97\xe4P3\xdf\xb1\xd2\xe0\xf6j\x14\xb9B?\x14\x18\x86^\ni\xf4\x80c\x82\xf4\x10-\"g\xda\x94v[\xf2\n\xb0\xb8\xf9\x15\xa4\xa3z\xb0\x8cP\xd9\xe92@DT\x0d\x9d\xfcH\xa5\x83\x8er\xbd\xce\xf7\x1aI\x04\x00\xb6\xf6\n{\xb6[D\xefPieO/o\x11\x94\x13\x92\xa9@\xf3\xcaSv\xcfP\xf4v\xf1?\xd0\xa1r\xfd\x11\xb2\xc2\xe2;\x86\xe0&\x7f\xc1y\x0e&&\xba\x028P\x1f\xf8\x88I\xb5(+$\x9c\xe7\x9b\xfc/\xceL}\x8e]\xa4\x96Y\xe9lxF\xc1\xaa\xfdy3\x85\xbc\x08N\xe4F\x057\x9b\x1f\x88\xc7!\xa1\x95\xdcTj\xa6\x80Q\xa72\xa3j\xa4\xd1\xd5\xc4=\xa9\xa8ZV3\xc5\x91\x1f\xe4\xd0\x81\xfa\\\xfd\x92\xdb\x00(\xcb\xa9!B5R\x90\x86R\xc1\x99\x90\x06b\xd4*\x04^\x13\x0c%\xb7p\xc0\x0d\xe2\x06/\x10@5p9Hk\n\x01\xae\xc2:{Yh\x9e~h>\xc3\xf9\xb6\x84\xc37\x93\x12\x859\x90\xdb\xb7\xc6\x0c\xf2A\xbc4\x12\xbb\x96OB\xb9:\xdd^\x93~\x85g$&\xd0\xcd\xa5Z \x0b\xd7Be\x03\xf7v`W*\xe1\xd3M\xb8\xa2\x84\xac\xa36\xff\xc3\xc3\x05\x06t:<@\xbc8\xe2mB\x86\x0d\xb2\x0b\x86\x13\xd7\x1e\xc1\x94c\x9c\x88\xc9\xae\xd4\xa5B)\x02\xe6c\xad\xbd\x91\x9eY\xebo\xd6d\x04\x90\xfc!\xf3\x91bK\x068\x9e|\xec\x88\x9d\x9e\xc8x\x87{\xe0\x07\x00\xfbVz\x1d(\xd9q\n]~\xbf\x12\x9fp\x0c\x0e\x89&\xac\xdb\xe6\x15\x89o\x80\xa9\xe9\x0b\xf6PM}\x815\x8e~\x8f\xa2%\x08\x1dH$X\xa0s\xaff\x1e\x9f\x0d2\x12\xb6)\xa7!d^U~9\xdf\x16J\\\xb3\xd7\xb9nv\x87\xaf\xabo\xcb\x1f \xe0\x15\xd7}I\xf6\x93\x83\xb8\x112l\x89^\x8f\x8f3\xab\xd0\xd2\xd8\xd1\xcd\x19c\x133\xe1L\xb8\x9d\x17\xdeaE6\xe2\xa6\xdf\x84\xbe\xd2,\xef\xe1\xe8\xb6\xa4\x07\xcb0B\x95\x8e\xb8d\xa3\xa4\xad\xa1\x81Y\x08\xa3\x80 \x0b\xb3\xd8\xb9\x99\\\xdd\x90j\x89z\\nC\xb0Hq\xbeU\xbe^`'\x03\x9eh\xd2\xe0\x0b\x08\x9f?\x17\x07iv\xe8X\xf7\x85\x96\x83\x9c\xa5\xfb,%5\x112@=q&\xa2\x95\x8eyY\x08\\[\xa8n(X\n\x9aYI\xeb\xea\xda\xe7\xeb;\x93\xf0\xd6[\xd1yu\xd7\xde\x03/\xb3\xc2\x9f\xf1U\xdb\xfed\xceG#\xb2\x9a\xf7\xb1\xed\x05[\x11\xceB\xd3\x1d\xcd\x0d\xef\x16\x84\x8b\xda\x13A:\xc7\x01\xa7\xd1\xed9\x0b\x01ii\xd4'\xf5\x87.\xa6\xf8\xcbX(\xa4kP\x06\x0cZ\x9d\xb3W\xe0#\xc4 \x99>\xf4D\xed\"\x0d\xc5\x16\xcd\x9d_IeF\xad\xef\xb5\xba\x9bK._oR\xf7\xe0\xa3\x18\xa7\xb9y\xcf\x8e\xe2\xa4`C\x90 \x8e\xeb\xfa;\xce\xf3\x9d-\xc4CO\xf5\x9c\x95\x10\xa4D\xe0\xf1\xe7\x05{sRn\n5i\xf2\xabc\xbf\x83\x89\xff4U\xe7\xach\x85\x02\xdb\xea;\x8d\xa6\xe9\xe1\x94\x15\xf2\xfb\x93u\xad\x06\xfe\xa7\xb1:\xa5S\x1a\xab_\x1d\x86\xd3p9\xd1\xf4\xac\xbf>\xb1\xe3W\xe7\xa7'\xbfN\xd6\xd9\xf3\xdbzw\xcfD\xef\x91e\x9b\x1e4\xfcT&1\x9eI\x91\x12)\x1b\x1a\xf3]>f\x92\x0f\xaf\xf6\x19\xb8R;\xc8A\xe8\xb3\x1di\xc9\xaaQ\x98\x978\xea\x86\x10g\xcfNQ\x97C\x0f\x10\xb60\x10\x10\xd2\x15\x96iS\xa3eJ\xde\xc8D\xd7\"\xe6`q\xa4\xe0\xba8Q{\xd0\xf5.\x1c'\xbc\x92\xa1\x90f'\xf1)\x9c\xa3\xf8\x06\x92WK\x0b\x8f\xe6\x0f\xb7x\xfb\xe3\x81\xa0R\xca>m\x05\xe6tu$6s\xf1\x92\xd5\xe1hN\xd1\xf8!\xbc \x04j\xab}\xb6\xa7\xa7\xb5\xfc\x1a[\x96.\x16$O\xa0\xcb\x97\x9fe,\x9c\xbf\x97J60SBN\xf6\xc5\xbb\xae0\xe4\xc6\x0c\xf5N\xd3ED\xc0+\x19+\xeaQH<\x8b\xdf.\x01I \xf2:!]1\"\x0dV-\x03\xdd\x92p}N\x96!\xb4{\xc6\x1bY\xe2\x90\xd3\xe2\x87\xcd\xd9\x08c\x01\xf5\xbd\x81\xfb5\n\xa6\xb0\xb1\x90J\x9f.e7\xb0\xa5\xb4\xdb\xc3\xe1\xd94\xeb\xd2%z\xf9O\x88\xf5i\xfe\xc3\xfaO\xa9\xe1\x95\x14\xcf\xeaH\x98\x8cp\x06\xad\xcfXD,\xda\x12|\xdb[\xd0\x9b=J)\xd7\x0b\xac.U\xacs\x0e\n\xd2\xa3\xa4\xe2\x1bo\x067\xd5{\xf1\x04\xc2n\xefIR\x80\xef$j\x1b\xd9\xed\x0b\x83Cmg\xaf9\x816\xc2d\xa8\xf6sr\x13\x84\x9a'\xd2\x87\xd0\xee\xf3\xed6b.=\x12\xaa\x8e\xc2\x14\xe6\xc0\xda\x81\xfa7+O\x1fxi/UBj\xb3Gl\"\x9a\x05U\x93`\xb4\xd4n\xb0\xa3,\xb3o\x06a\x82\x893\xe6,\xaa]\xf8\x00\xf6W\xce\xf6\xa6\x8f\xa9Zt]\xf6\\%\x06,C\xcf\xf4x\x95\xf3'\xb5\xcb\xeb\xd7s\xd8a\x18\xe6K\x0f\xe6\x00\x8ex\x1a\x94\xad \xd7\xdf\x92\xf5\x15\xc4R1\xb2Q1\x8f\xb7D@.\xa5\x00\x87\xfd\xdd\x00\"\xf7!\xb3Y\xb81\xd1|\x9f6\x8c\x9bIy\x818.\xe6{R\x19\x15\x82&:\xff\xb3\xc7~\x18\xa4\xf5\xa2dT\xd1\xc8\xb87 \x97\xdbt\xe7\xabL\xbe\xfc\x94\x10\x85R\xc6S2\x1as\xe5\xd8\xbc9\xeb\xbd\xe1\xf2\xb5\xf5\xe3R)\xa7}X{f\x9d~\x0cI\x07\x88\xb6\xbfaI[\xb9\xa9BH[\xfc\x84\x85F{\x8fdB\x93\xca\xd0 l\xb8!\xb6\ne\xa8\x96e\xd3bp\x80\x83\xb6=6e\xd3\xce|\x81x4\x11L\xd8 \xdb.\xcd%\xa1\xcbV\xe3W\xcb\xbb\xfd\\\xfb\xb5\x8d\xd4W5\xdd\x9aA\xcao\x16DQ\xf5\xb7>>\x9f\xb5\\\xb7\xb9bm\x1f\x02\xbd}\x8b<]\x95\x9cr\x1dH3]t\xa0\xb8\x99\x84\x8f\x93\xe3\x9f\xf7!;\x94=\xf6P\x1aZh\x8eE\x02\x1a\xa9\x9dl\xd3\xd9\x03\x07.O\xf0\xd4\xbb\x13\xe9ew\x05\x19\xb5\xbb>\xe2\xcd\x15\x8e\xfa\xd0]\xc0\xa8\xc1\x80\xc3%\xe3\xa8+\xce\x97\xceY\xc7u\xf5\"\",\x9e\xb1\xb9\x02\x8d\xab?\xceKf\xb3\xcdG8,\xa4/O\xa7\x85\x8b\xa6v\x98\x83\x9f\xd8\x94\x7f\x03\x03O\x00\xc3\xa4\xa6\x82\xf44\xe1\xb2\xf2\x98~\xccB\xf0\x9c\xedF\x98:*\xe9\xe29\x03\x14\x0c\xa8\xd3\x07\x92\xb1\x89q\x15\xc4\xf3\x8e/Dg^p'G\x89U\xaf\xb2\x81i\xc8\x94!\x86\xda\xe4 \x83V@\xf0+']\xe0V\xda\x9eE7'\xe5\xbe8!\x80\xe0\"]gH\x19\xf7\x83\xff\xb2\x87O\x11\xd5\xee\\\xfa\x9b\xf6\xae\xf5\xec\xa9\xfaG\x9dF\x1d\x17\xd0\x95lT:\xba\x08\xe0\xd21\x909\x19\xcd\xa9B~\xb6\x932fS\x1c\x8c\xadL\xc8\xac\xf4\x01\xc9C\xe1\x01*\xbf<\xe0\xfbW\xa1\x18\x1e\x17\x12Z}\xee6c\xb7\x08e\xcd\xe7\xf22\x1fn\xe5g]Ev\x18\x9b]<\x97^UK_\xbe\x94\x12Fh\x07\xddHH\x99\x02\x12G\xcc\xa4\xa5\xaex\xf2\xd6J\xe7\xd1\x16\x17\xb0\xdf\x14L\xb1\xd1u&\x83\\\x0e\x91\x80\xa4\xb1h\xc2\xd1\xa1\x84\x8av\x94\x92\n\xaaj\xf8\x1bG\x80\xad\x10u\xf9[\xa9\xf2\xd4\xa3\xeerQ\xf9H\xe6\xe4\x053\xb0\x86@\xf9\xdd,\xdd\n\x93\x91\x80L\x83}\xf1Cx\xbd\x0f\xaf\xa1\xc8\xbe\xffJ\xa8\xdc4\xcaL80\xcd\x13:\x9a\xd3vT\xe7\xb7\xf9\x94\xfe\xdf\xf8T\xf1\xef|\xea\xf2\xfc\xc4\xf6\x8e\xbe\x85\xe6?0\xaa\x81\x8eN5\xd0\x91\xaf\xcf\xc08\xb5\xd0)\xba&7*zj\xaf\xe1\xc5\xc9\xd8{\xa0\x93\x95\xa1\x9e\x8c\xe2)\xca\xe8*I\x8f\xb2\xc5\x10\x97O\x89\xee\xee IU\x9a~\x01N\xdcJk,\x9f\x1b\x81\x0b\x84\xfe\x97\xc2\xdb\xa5\x97e\xbb\xb6\xa0\xb6\xa8\x1b\xddc \xf2\x92\x0c\xe7\xa49H\xbdQ\xc7\xe5\x18\xd0)\xd1(\xc2`l\xba%\xd6\\\xc2\xe9/\xbb\x10c\xac\xa4Y0 \xb7\x81O\xb8Y\xf1I\x1a\x93Nqu\x00/P\xd4\x01n\xe5.B!\xd7\xd9|\xd7|\xcb\xf8\x82\x0e\x99\x1b\xee\x17\xf2h\xcen\xe5j\x8f\xa2h\xcf\xa5=N\xb2\xc1]\xf31\x02\xbc}\xbc?'\xede\xea\xf5|\x85T\xfb\x82\x97r\x81\x86&\xa6\xc5-\xf9\xde}\xb7 m\xa4V\x1e(%\x17J\x81L-#M\x83]\xe4\x86\xfaF\x0fJ;\x95.\x95\x84\x94\n\x06\xfc\x0e\xb2\xa53\xf0\xc1J\x802\xb4\x7f\xc8E)\xaa\x98W\xd7\xb1\x15\xben\x9f\xb2{8\xbd~8\xc3\xedOj\x1bgru\xaa\x94\xce\x12 \xa1\x01 \xa8\x07/\xa4\xbd\xabd\xd8S/\xf2\x1b\xad\xb4N\x01\xce\x9c\xcf\xa7\x01\xefE\xcb\xe6\xa3J\x13\xd5\xbfSj[\xef\xb35,2M\xc8\x89\xd9\xd6\xec=\x1f\xe0\xc2\xa8\xceP\xb5\x10\x8b(\x97\xa5} \xf3 ]u#\xef\x94\xa5o\xb3\xd7\xb0\xc1\xc2;0\x9c%\x84\xdd\x0f\xb9\xb2a?\xbc\x93 \x8c\xe44\x1d\x16\xd9\xc3\xe0\xdb\x84+)\x9bH%\x13\xaf\xb1\xd7hO\xa6\xf5\x84v;\x80\x00\x14v\xd9\xfc\xcc]C\x1c\xaa\xa0\x018\xe8\x98\xdfz'=&\xc2\x1a\xcb@I\x03\x8b\xb6\\\xbf\xbe\x92KV\xa4\xa4\xc1G[\x82]\x84\xdc\xb3\xa7lm+w\xe0|3\x8a\x10{\xc0\xc8\xe9\xa4\x88\xb6\xd4X[\\\xa2+\xac\x17t=u\x04\x06\\\xf9,\xfds'\\\x02\xa9\xc1\xf5u\xf7>\xde>\xe2f\x18\x0e\xea\xecE\xc1FKTw\xad\x89r\xc4\x85L\xbf\xe8jF9\xd7O\x17-\x97\xa4\xf3@\x85\xcf,s\x1a\xc0w-\x1d\xb1P\xee\xb2\xf5)\xb1\xa4\xd2\xec\xc8\xa5\x0b\x9c\xc8\x8d\xe4\xaa(8\x1d\xc2\xa7\x80\xe4t- \xd9A\x83\xde\x17\xc7\xd2 \x02D\x16\x8f\xe0\xd5\xaf\x040i\x8a>(\x97:\xf2\x11\xc0)\xcd\x91o\xb1\xeb\xdc-\xafB\xf6Q\x07Rt\xd1\x0cJn4S+,\x1c\x8eH\x88\xb74\xa9\xaa+\xf6\x83&\x1d\x9d\x85\xdb\x8fK\x8ce\x8c\x14($\x94\\\xc6Cn \x1a\x94J|`\xf1\xfc\xb1\xdct\xa6;\xd4\xeb\xd2\x1a\xa3\x9b\x104\xd0\x85\xae\x80\x87\x08Z\x94\x90\xa0rZ\x18\x90\xcbV\xd2\xe2Cw\x85\x9b\x83\x9e{\xd8\xa0\x07\xe1\xe7\xdd\x15\\\x1aT\x96o\xa4\xb0i\x17\xf9@\x88:j#\x15]\x8e\xee\x93\xb2\xb7\xbc\x15\xe5\xbb~|gXRfX\x92\xf6 l\x81Xh\xe5\x8ek\xf0_wD\x17t\x1d\xe1\x92\xc8N*\xb7|\xd4\x18\x9b\x94\xe6\xaeJ\x06\x81m\xd7\xab*Xh\xbaIS\x88W\xe6R^\n\xbc\xd1ew\xfas\x80\xe4\xe5\xc7\xa7\xe2c\x8d\x80\x16\x95\xe9c\x94g\xa4#\x96J\x7fX\xa8\x10\xddm\xb4\x92u\xe4\xf7\x8es\xd7V\xa6Y\xae~\xf7\xe9\x1d-2\x8b\x10i\x97\xde\x83\x90T\xc8 6\xdeIE7\x9a\xeaY-\xd7l\xe4\x86q\xa0\"L:\x00NmH[:O_}\xe2\x94\xa8\xd2\xea#\x1c\xa2\x9fe\xa6\xa0\xa5\x9c\xb3\xffU.:6o\x05\x1e\x02s?\xba.ZlA\xa1C\x8c2\xd4\x80l\xbeo\xcau\x0d\x19\x93\xd1\xc5\xbf&;`!\xb4\xfb!\xd7\xff\xb2rs\xf1V\x06br\xdf\xac\xee\xaeZC\xf2\xf2g\xdf)\xbb\xa1\x92\xb4EI\xadX\xe17}st\x9aM\xb1\xde\x9a\xec\x8e$\x87\xf2v\xe84+6\xf7\xf0\x9c\x83]\xf5\xf5;\xceL\xd4j\xa8]-\xfd@\xf5\xfa\x94\xf6\xc1\x83\xe53\xd8\xe9X\xe6\x17\xa8H\x92 uQH7eHO\x0c1\x97\x96g-\n\xaa\xd9\xa4\xce.\xc8 \x16 \xa2]}\xca\xe5\x1a5>%\xbbR\x05\x03\x04ne\xc2\x0c2\xee\xca#6|-\x80W\x93\xde\x0e\xbb\xdb\xbe\xa5D\x9e\\NS4\x96\x95\x18E\xb8Q\x9aK\xcb\xcd\x15d\x88\xa7G\x88\xd1'\xff7\x93\xbb:\x91\x1b^J&T\xff\"oK\x7f\xe1l\xcc\xe4\x14\xa7\x19%\x1d\x8c\xbf\xfb\x0b\x9d\xdf\xf9[\x96_\xfe*\xe7\xef_\xae#\x10\xbf\xbc\xff_\xfe\x9a\xe7\x1f\xc9\xfd\xaf\xbf\x99\xf9\xbdn\xe1/\x7f+\xf3\xf7/\x96\xbd\xf7\xd77\x7f\xeb\x0fl\xfe\xc9\x11\xa49\xf9[\xe4\xfc\x97\x9e\xe4\xdf\xbfX|\x99_\xf2\x9b\xff\xa0\x91\xf9\x8f\x0e\x81\x8a\xe07\xfd\xf2\x97B\xe0\xef_|\x88\x1b\x99\xb9\xfd\xf5\x10\xbfQ=\xd0\x19\x8a\xcec\xff\xed\x95\xff\xfa\x93\n\xed\xbf8*\xa1\xd6\xff\xe7\xb8\xf7\xef\xf5\xf5\xb5\x97\xe3\x1b\xe1\xe0\xe8\xf2*\xf4\x86\x1b{eG\xee\xf5\xdd\xde\xeb\xfb\x7f\x01\x00\x00\xff\xffPK\x07\x08\x80\x00\xa2-\x1b\x19\x00\x00\x897\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x94\"+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00 \x00themes/dracula.tomlUT\x05\x00\x01\x19\xfbZ_\xccW\xcbr\xea:\x10\xdc\xf3\x15\x14w\x1b\xa7\x82\xc1\x0f\x16\xf7KRY\x8c\xe5\x11\xa8\"?J\x96\x92\xc3\xdf\x9f\xb2dd\xf0C\xc8\xa9\x13\x12\x96=\xd3n\xcdH\x1a5+U\xaa\x06\xf3\xf5\xff\xeb\xcd\x7f\xc9\x01\xb7[\xd8\xacV\xaf\xb9\x00\xa28\xbc\xad\xd6\xeb\x0c\x1a|y\xd1\xf10\x0da\x17o.\xe0V\x83\xbbd\x97\xee\xf7\x16\x0c5\xb8\x8f\xf7I\x14Zp\xa7\xc1(\x8e\x92xk\xc1\xbd\x06\xb38\x8b\xb3>3\xd2 i\x7f\x89\x05c\x0db\x88aN,\x98h\x90\xa64\xa5==5 \x8d\xa2(\xb2\xe0\xa1\x03\xb34\xee\xe9`\xc0-\x85\xb4\x073\xb3\xce\x17\nIfA\xa2\xc14\xc3\x03\xcd-h\x1a\x16\x87I\x08}\xedh*\xca\x0f;z\xb0 \xed\xd4\x93\x031\xad\xe3@\xde[\xac\xeb\xf1\xb3i\xb0 )\x1cE\xb4&9C9\x8a\xe8u\x1f\x05\xe28\xa4W_\xc0\x11K \xa3 m\x83\x95\x80\xf28V\xd3\xeb\xae\x95\xa8\xf98\x86mL\x98\xb3r\x13H\xdb\xc0\xe7\x89\xc91Go\xe2\x199\xaf>G1s\xd2@\xbc\xebc\xa6\xa4\xac\xdaB^m\x92\xfe\xe2\xe6\xa9'\xb5\x9d\xdb\xf4\xc9\x01\xad\x88jn(&\xa5\xa7\x98J\xae9\x0dr$R\x17\xe1\xa3D\x8a\x9c\xb3\x12\x9d\"\xa6\xbc\xeb\xf4`\xa2\x9a.kR\xa4\xaa\xcfAQ\xe5\xfe2\x17B\x00\\.V 8d\xc8\x9d\x0d\x10\x98\x1b\x8e\x12\x02K\x19\x10\xa8\xa5\x128\xc9\x91',\xb0y6sD\xb3r\x06\xbc:\xfa\x16c\xb2\x17\xb6,G\njP\xfb\xfc.R\xc6%\x8a\x80\x95\x12E\x819\x03\xe9n\xb5\xb9\x1c\xb7\xd4\x0f\xe0,w\xb2.M\xeb(\x05\x96j\xd9\xfa\xeeK\xe8\xdb\xae)'\xfc\x13dg\x89\xd3\x07\xdaq\x0f,Q\x95\x0b\xefB\xcb\xa4\x0cy\xee\xa7\xd9\xce\xac\x01oFr\xc84K\xb8P\xf5\xae}\x00\xf7\xbc\xba\xfa\xbd\x19\x93}\xab\xd5o\x98\xa5s8\xa3\xf8\x92\xb0a.W\xad\x81\xbc\xa3\x0c8kd@\x90\xf3eCn\xc4\xf6\x97\xdf\x8d> \xaa\xcf)u\x93<\xb1\xcfC\xae\xbf\xf6M\xe9\x8d\x14\x8aH\x8f\xba\x87\xc2\x1d\xd1\xeb\x88]\x97,\xaa\xa3\xc0\xa6 HU\xd4\x1c\xe5\xf4\x94\x9bj\xf6\x85\xb8l\x18YZS\xb3\xb2D\xe1\x1eyWz^\xf9\xbdN#\x05B\x11\x10\xce\xb0\x94^\x83\xabc\x14 \xc9\xc9wzw\x9c\x06A\xdc!\xf5\xb7\xdar\xc4\xc7\xa0\x1c\xbd\xa5\xd7\xc5(\xc3\x90L\xf2\xdb}i\xd7\xfc\xb4\xb1\x8f\xce\xea\x95\xb3\xe3I\xbeM;\x89\xf9\xa5\xfck'1y\xce\xf6\x9a\xf6\x83Vbx\x18\xbff%\xfa\xae\xb9\xac\xc4\xdc\xe1rY\x89\x0b\xe7\xe7=\xc4\xfcI\xf9\x1d\x1e\xe2\xee\xfa\xeeK<\xc0C\xb8\x1f\xf4\x87x\x889\x1f\xe0\xff(\xf9\x9b\x88I\xed\xc8\xd2\xbf\xc9D8T\x1fa\"\x1c\x93\xce\xc3D\xdcy\xca\xdd\x1e\xe2~\xe5\x8f\xf3\x10\x9d\xec\xaf\xf6\x10W\x9e\xc0'}\xf4POX\x88\xb9?\x8c\xb3\x16\xc2\xf1\x04\xcd[\x88\xf9\x06|\x83\x85\xf8\x1b\x00\x00\xff\xffPK\x07\x08\x97\x1e\x94\xb8\xfa\x02\x00\x00\n\x13\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x88!+Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00 \x00themes/solarized.tomlUT\x05\x00\x01!\xf9Z_\xd4W\xc9n\xe38\x10\xbd\xfb+\x0c\xcfu4\xb0,[\xcba\x0e\x93\xe9\xee\x9f\x08r(\x91%\x9b\x08\xb5\x80\xa2\x92v\x7f}\x83\xa4E)\xdae F\xfa\xfa\x8a\xaf6\x96\x8aO\x9b*\xabJ\xa4\xdb\x7f\xb7\xbb\xbf\x82\x08]\x17v\x9b\xcds\x99s\x10\xec\x17\xd2\x97\xcdv\x1bC\x89\xfb\xbd>\xb1\xdf\x1fb\xcf\xdf\xd5\xa0k\xc0\xc0\xf3\x8f\x07\x0b\x1e4x\n}\x0cN\x16\xf44\xe8\x9f\x828\xf4,x\xd4`\xe8E\xc7\xa8\xf1y\xd2`\xe4\x81\x0b\xae\x05}\x0d\"bH\x1b\x9f\x81\x06\x13\x9a\xf8\xd8\xf8\x0c5H\x89w8$\x16\x8c4H\xe2c\xec6\x81@\x83O\xa70\xda\xef-\x18\x9b\x94NQ\x1b$\x1a<\x00\xb8QhA\xd3\xb2\x83\x1f\xc6\xb4\xa9\x1dM\x99$p\xc9\xd1\x82\x89I\xc9\xf3\xfc\xd0\x9c\xe4@^\x15f\xbb\xfc\x8fi\xb11V8`\xfb\xa6l\xe4\n\xd9\x80\xed\x7fe;\x0b\xc4!\xe3\x932\xa6p\xc6L\xc2\x80\xf9\x872\xe7\x02\xb2\xf3P\xd4HY\x8bJ\x14|\xc8\xfa]Y\x85\x99\x9d\x8eI\xb7\xe9\xfd\xc2\xe4\x10/P\xc6+r\x9e\xbf\x0fX\xffS\xf3GA\xbc\xea\xd1\xab\xa4\xccUY\xcf\xadc\xda\xef\xee\xef6Qut\xd7\x10\x9c$'U\xd9\xa1\x99Cm\x9a\xa9\xac\xcd+\x91#\x91\xba\xa4\xa5\x11IJ9\xcbp6\x98)\xb8Mq\x06\xab\xbb\x9d\x1b \x96\x17W'\xcd\xe9\xbap5\xc9\x01.\xef\x8a\xe6p\x88\x91\xcf6E 5\xbcJ\x08\xcc\xa4C\xa0\x90\x95\xe8\xe6Z\xf3\xcc\xe2\xd1\x04\xca\x80\xe7\xe755\x19\xc6\x1d\x1d\xa4\x98@\xd5k\xc3\xd4\x05'\x8cK\x14\x0e\xcb$\x8a\x14)\x039\xdf}\xf3E}\xa4\xbf\x01g\xdd\xc1\xea3\xeb\x1e\xdeh)f\xd5\xfa\\\x97\x85\xd2+C\xd3.\xf8\xd3\x89\xaf\x12\xc7\xe6\x7f\xf2\xd3\xb1\xe4*\xbb\xe3\xf3Q\xec\x84!\xa7\xcbc\xab5\xd8\xe1\x8e\x86\xee\xb3M25]\xdf\xea\x1b\xf0\x15_\xbe~\xce\xfa\x0e\xd6T\xaf\x9fI\xeb\x82\xc3\x15\xc5\xdd \x18\xf6}\xd1\x0b \xaf(\x1d\xceJ\xe9\x10\xe4|\xfd\xde\xecyX\x97\x86\xd7s\"\xf2\xf7\xe1,\xcc\xf1\xc19\xe8\xf2\xd7\xe5\xf0\xa1\x15\xa5\x14\x15\x91\x0b\xfb\xd0M\xe0F^<\x8a\xed\x16\x88\xfc,\xb0,\x1d\x92\xa7\x05\xc7\xde\x8a\xe9\xe7\xde\xbe\x84\x9a\xbc~\xb9YjY\xb0,C1\xbfJ[q\x17s\x9ax\xa5\x14\x08\xa9C8\xc3\xac\x9b\xe7\xf82\xbc\xb1R\x90\xe42Kj\xbd\x127^\x89 \x16\x10\x9b\xcd`y\xe2\xadW\x9e\xbe\xf4\x8f\xc5U\x86%\x99\xe4\xdd{S\x15\xb4\x9e\xba\xcd3g\xe7\x8b|\x19\x936SI}\x8e\xb4\x19\x99\xca\xa3\xa6~ m\xd3\x1f\xdf\xfb\xb5M\xd3\xcdim3>\x8a\xd3\xda\xa6\xe6}%m35S_M\xdb,\xc8uY\xa8\x87i\x9b9\x81\xf1Pm3\xa6M\xd6=\x88k\xc4\xcdH\x0e'\xeb\xe2S\xc5\xcdd\xf4\xc7\x89\x9b\xc9\x1d\xbaH\xdc\xccJ\x8b9m\xb3\xa4\x13\x8f\xd76\xb7\xf0\x7f\x8c\xb6i\xe9\x94\xa5\x94\x9eh\x18\x946\xe3\xff\xca\x13\xd2f\xf2\xe1\x9b\x926SM\xf9