Better traversal; added persistent list expansion state

This commit is contained in:
diamondburned 2020-09-02 00:11:48 -07:00
parent 8838b8e0d8
commit 35c186580b
13 changed files with 307 additions and 116 deletions

View File

@ -90,7 +90,25 @@ func Save() error {
} }
// Restore the global config. IsNotExist is not an error and will not be // Restore the global config. IsNotExist is not an error and will not be
// returned. // logged.
func Restore() error { func Restore() {
return UnmarshalFromFile(ConfigFile, &sections) if err := UnmarshalFromFile(ConfigFile, &sections); err != nil {
log.Error(errors.Wrap(err, "Failed to unmarshal main config.json"))
}
log.Printlnf("To restore: %#v", toRestore)
for path, v := range toRestore {
if err := UnmarshalFromFile(path, v); err != nil {
log.Error(errors.Wrapf(err, "Failed to unmarshal %s", path))
}
}
}
var toRestore = map[string]interface{}{}
// RegisterConfig adds the config filename into the registry of value pointers
// to unmarshal configs to.
func RegisterConfig(filename string, jsonValue interface{}) {
toRestore[filename] = jsonValue
} }

View File

@ -2,6 +2,7 @@ package config
import ( import (
"encoding/json" "encoding/json"
"io"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
@ -33,6 +34,13 @@ func __init() {
} }
} }
// PrettyMarshal pretty marshals v into dst as formatted JSON.
func PrettyMarshal(dst io.Writer, v interface{}) error {
enc := json.NewEncoder(dst)
enc.SetIndent("", "\t")
return enc.Encode(v)
}
// DirPath returns the config directory. // DirPath returns the config directory.
func DirPath() string { func DirPath() string {
// Ensure that files and folders are initialized. // Ensure that files and folders are initialized.
@ -41,6 +49,24 @@ func DirPath() string {
return dirPath return dirPath
} }
// SaveToFile saves the given bytes into the given filename. The filename will
// be prepended with the config directory.
func SaveToFile(file string, v []byte) error {
file = filepath.Join(DirPath(), file)
f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_SYNC|os.O_TRUNC, 0644)
if err != nil {
return errors.Wrap(err, "Failed to open file")
}
defer f.Close()
if _, err := f.Write(v); err != nil {
return errors.Wrap(err, "Failed to write")
}
return nil
}
// MarshalToFile marshals the given interface into the given filename. The // MarshalToFile marshals the given interface into the given filename. The
// filename will be prepended with the config directory. // filename will be prepended with the config directory.
func MarshalToFile(file string, from interface{}) error { func MarshalToFile(file string, from interface{}) error {
@ -52,10 +78,7 @@ func MarshalToFile(file string, from interface{}) error {
} }
defer f.Close() defer f.Close()
enc := json.NewEncoder(f) if err := PrettyMarshal(f, from); err != nil {
enc.SetIndent("", "\t")
if err := enc.Encode(from); err != nil {
return errors.Wrap(err, "Failed to marshal given struct") return errors.Wrap(err, "Failed to marshal given struct")
} }

View File

@ -148,7 +148,7 @@ func (h *Header) SetBreadcrumber(b traverse.Breadcrumber) {
return return
} }
h.breadcrumbs = b.Breadcrumb() h.breadcrumbs = traverse.TryBreadcrumb(b)
if len(h.breadcrumbs) < 2 { if len(h.breadcrumbs) < 2 {
return return
} }

View File

@ -1,3 +1,5 @@
// Package config contains UI widgets and renderers for cchat's Configurator
// interface.
package config package config
import ( import (

View File

@ -112,7 +112,7 @@ func (h *Header) SetBreadcrumber(b traverse.Breadcrumber) {
return return
} }
if crumb := b.Breadcrumb(); len(crumb) > 0 { if crumb := traverse.TryBreadcrumb(b); len(crumb) > 0 {
h.SvcName.SetText(crumb[0]) h.SvcName.SetText(crumb[0])
} else { } else {
h.SvcName.SetText("") h.SvcName.SetText("")

View File

@ -0,0 +1,109 @@
package savepath
import (
"bytes"
"time"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/log"
"github.com/diamondburned/cchat-gtk/internal/ui/config"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
"github.com/pkg/errors"
)
// map of services to a list of list of IDs.
var paths = make(pathMap)
type pathMap map[string]pathMap
const configName = "savepaths.json"
func init() {
config.RegisterConfig(configName, &paths)
}
// ActiveSetter is an interface for all widgets that allow setting the active
// state.
type ActiveSetter interface {
SetActive(bool)
}
// Restore restores the expand state by calling SetActive. This is meant to be
// used on a ToggledButton.
func Restore(b traverse.Breadcrumber, asetter ActiveSetter) {
if IsExpanded(b) {
asetter.SetActive(true)
}
}
// IsExpanded returns true if the current breadcrumb node is expanded.
func IsExpanded(b traverse.Breadcrumber) bool {
var path = traverse.TryID(b)
var node = paths
// Descend and traverse.
var nest = 0
for ; nest < len(path); nest++ {
ch, ok := node[path[nest]]
if !ok {
return false
}
node = ch
}
// Return true if there is available a path that at least matches with the
// breadcrumb path.
return nest == len(path)
}
// SaveDelay is the delay to wait before saving.
const SaveDelay = 5 * time.Second
var lastSaved int64
// Save saves the list of paths. This function is not thread-safe. It is also
// non-blocking.
func Save() {
var now = time.Now().UnixNano()
if (lastSaved + int64(SaveDelay)) > now {
return
}
lastSaved = now
gts.AfterFunc(SaveDelay, func() {
var buf bytes.Buffer
// Marshal in the same thread to avoid race conditions.
if err := config.PrettyMarshal(&buf, paths); err != nil {
log.Error(errors.Wrap(err, "Failed to marshal paths"))
return
}
go func() {
if err := config.SaveToFile(configName, buf.Bytes()); err != nil {
log.Error(errors.Wrap(err, "Failed to save paths"))
}
}()
})
}
func Update(b traverse.Breadcrumber, expanded bool) {
var path = traverse.TryID(b)
var node = paths
// Descend and initialize.
for i := 0; i < len(path); i++ {
ch, ok := node[path[i]]
if !ok {
ch = make(pathMap)
node[path[i]] = ch
}
node = ch
}
Save()
}

View File

@ -208,8 +208,12 @@ func (s *Service) MoveSession(id, movingID string) {
s.SaveAllSessions() s.SaveAllSessions()
} }
func (s *Service) Breadcrumb() traverse.Breadcrumb { func (s *Service) Breadcrumb() string {
return traverse.TryBreadcrumb(nil, s.service.Name().Content) return s.service.Name().Content
}
func (s *Service) ParentBreadcrumb() traverse.Breadcrumber {
return nil
} }
func (s *Service) SaveAllSessions() { func (s *Service) SaveAllSessions() {

View File

@ -5,6 +5,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/gts" "github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives" "github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/service/loading" "github.com/diamondburned/cchat-gtk/internal/ui/service/loading"
"github.com/diamondburned/cchat-gtk/internal/ui/service/savepath"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse" "github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
"github.com/gotk3/gotk3/gtk" "github.com/gotk3/gotk3/gtk"
) )
@ -180,6 +181,9 @@ func (c *Children) LoadAll() {
row.Show() row.Show()
c.Box.Add(row) c.Box.Add(row)
} }
// Restore expansion if possible.
savepath.Restore(row, row.Button)
} }
// Check if we have icons. // Check if we have icons.
@ -227,6 +231,6 @@ func (c *Children) saveSelectedRow() (restore func()) {
} }
} }
func (c *Children) Breadcrumb() traverse.Breadcrumb { func (c *Children) ParentBreadcrumb() traverse.Breadcrumber {
return traverse.TryBreadcrumb(c.Parent) return c.Parent
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu" "github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage" "github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage"
"github.com/diamondburned/cchat-gtk/internal/ui/rich" "github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/diamondburned/cchat-gtk/internal/ui/service/savepath"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/button" "github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/button"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse" "github.com/diamondburned/cchat-gtk/internal/ui/service/session/server/traverse"
"github.com/diamondburned/cchat/text" "github.com/diamondburned/cchat/text"
@ -24,7 +25,19 @@ func AssertUnhollow(hollower interface{ IsHollow() bool }) {
} }
type ServerRow struct { type ServerRow struct {
*Row *gtk.Box
Avatar *roundimage.Avatar
Button *button.ToggleButtonImage
parentcrumb traverse.Breadcrumber
// non-nil if server list and the function returns error
childrenErr error
childrev *gtk.Revealer
children *Children
serverList cchat.ServerList
ctrl Controller ctrl Controller
Server cchat.Server Server cchat.Server
@ -49,7 +62,7 @@ var serverCSS = primitives.PrepareClassCSS("server", `
// hollow children containers and rows for the given server. // hollow children containers and rows for the given server.
func NewHollowServer(p traverse.Breadcrumber, sv cchat.Server, ctrl Controller) *ServerRow { func NewHollowServer(p traverse.Breadcrumber, sv cchat.Server, ctrl Controller) *ServerRow {
var serverRow = &ServerRow{ var serverRow = &ServerRow{
Row: NewHollowRow(p), parentcrumb: p,
ctrl: ctrl, ctrl: ctrl,
Server: sv, Server: sv,
cancelUnread: func() {}, cancelUnread: func() {},
@ -84,12 +97,30 @@ func (r *ServerRow) Init() {
} }
// Initialize the row, which would fill up the button and others as well. // Initialize the row, which would fill up the button and others as well.
r.Row.Init(r.Server.Name()) r.Avatar = roundimage.NewAvatar(IconSize)
r.Row.SetIconer(r.Server) r.Avatar.SetText(r.Server.Name().Content)
serverCSS(r.Row) r.Avatar.Show()
btn := rich.NewCustomToggleButtonImage(r.Avatar, r.Server.Name())
btn.Show()
r.Button = button.WrapToggleButtonImage(btn)
r.Button.Box.SetHAlign(gtk.ALIGN_START)
r.Button.SetRelief(gtk.RELIEF_NONE)
r.Button.Show()
r.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
r.Box.PackStart(r.Button, false, false, 0)
serverCSS(r.Box)
// Ensure errors are displayed.
r.childrenSetErr(r.childrenErr)
// Try to set an icon.
r.SetIconer(r.Server)
// Connect the destroyer, if any. // Connect the destroyer, if any.
r.Row.Connect("destroy", r.cancelUnread) r.Connect("destroy", r.cancelUnread)
// Restore the read state. // Restore the read state.
r.Button.SetUnreadUnsafe(r.unread, r.mentioned) // update with state r.Button.SetUnreadUnsafe(r.unread, r.mentioned) // update with state
@ -151,60 +182,13 @@ func (r *ServerRow) SetUnreadUnsafe(unread, mentioned bool) {
traverse.TrySetUnread(r.parentcrumb, r.Server.ID(), r.unread, r.mentioned) traverse.TrySetUnread(r.parentcrumb, r.Server.ID(), r.unread, r.mentioned)
} }
type Row struct { func (r *ServerRow) IsHollow() bool {
*gtk.Box
Avatar *roundimage.Avatar
Button *button.ToggleButtonImage
parentcrumb traverse.Breadcrumber
// non-nil if server list and the function returns error
childrenErr error
childrev *gtk.Revealer
children *Children
serverList cchat.ServerList
}
func NewHollowRow(parent traverse.Breadcrumber) *Row {
return &Row{
parentcrumb: parent,
}
}
func (r *Row) IsHollow() bool {
return r.Box == nil return r.Box == nil
} }
// Init initializes the row from its initial hollow state. It does nothing after
// the first call.
func (r *Row) Init(name text.Rich) {
if !r.IsHollow() {
return
}
r.Avatar = roundimage.NewAvatar(IconSize)
r.Avatar.SetText(name.Content)
r.Avatar.Show()
btn := rich.NewCustomToggleButtonImage(r.Avatar, name)
btn.Show()
r.Button = button.WrapToggleButtonImage(btn)
r.Button.Box.SetHAlign(gtk.ALIGN_START)
r.Button.SetRelief(gtk.RELIEF_NONE)
r.Button.Show()
r.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
r.Box.PackStart(r.Button, false, false, 0)
// Ensure errors are displayed.
r.childrenSetErr(r.childrenErr)
}
// SetHollowServerList sets the row to a hollow server list (children) and // SetHollowServerList sets the row to a hollow server list (children) and
// recursively create // recursively create
func (r *Row) SetHollowServerList(list cchat.ServerList, ctrl Controller) { func (r *ServerRow) SetHollowServerList(list cchat.ServerList, ctrl Controller) {
r.serverList = list r.serverList = list
r.children = NewHollowChildren(r, ctrl) r.children = NewHollowChildren(r, ctrl)
@ -230,7 +214,7 @@ func (r *Row) SetHollowServerList(list cchat.ServerList, ctrl Controller) {
} }
// Reset clears off all children servers. It's a no-op if there are none. // Reset clears off all children servers. It's a no-op if there are none.
func (r *Row) Reset() { func (r *ServerRow) Reset() {
if r.children != nil { if r.children != nil {
// Remove everything from the children container. // Remove everything from the children container.
r.children.Reset() r.children.Reset()
@ -244,7 +228,7 @@ func (r *Row) Reset() {
r.children = nil r.children = nil
} }
func (r *Row) childrenSetErr(err error) { func (r *ServerRow) childrenSetErr(err error) {
// Update the state and only use this state field. // Update the state and only use this state field.
r.childrenErr = err r.childrenErr = err
@ -273,21 +257,29 @@ func (r *ServerRow) HasIcon() bool {
return !r.IsHollow() && r.Button.Image.GetRevealChild() return !r.IsHollow() && r.Button.Image.GetRevealChild()
} }
func (r *Row) Breadcrumb() traverse.Breadcrumb { func (r *ServerRow) ParentBreadcrumb() traverse.Breadcrumber {
if r.IsHollow() { return r.parentcrumb
return nil
}
return traverse.TryBreadcrumb(r.parentcrumb, r.Button.GetText())
} }
func (r *Row) SetLabelUnsafe(name text.Rich) { func (r *ServerRow) Breadcrumb() string {
if r.IsHollow() {
return ""
}
return r.Button.GetText()
}
func (r *ServerRow) ID() cchat.ID {
return r.Server.ID()
}
func (r *ServerRow) SetLabelUnsafe(name text.Rich) {
AssertUnhollow(r) AssertUnhollow(r)
r.Button.SetLabelUnsafe(name) r.Button.SetLabelUnsafe(name)
r.Avatar.SetText(name.Content) r.Avatar.SetText(name.Content)
} }
func (r *Row) SetIconer(v interface{}) { func (r *ServerRow) SetIconer(v interface{}) {
AssertUnhollow(r) AssertUnhollow(r)
if iconer, ok := v.(cchat.Icon); ok { if iconer, ok := v.(cchat.Icon); ok {
@ -297,7 +289,7 @@ func (r *Row) SetIconer(v interface{}) {
} }
// SetLoading is called by the parent struct. // SetLoading is called by the parent struct.
func (r *Row) SetLoading() { func (r *ServerRow) SetLoading() {
AssertUnhollow(r) AssertUnhollow(r)
r.SetSensitive(false) r.SetSensitive(false)
@ -307,7 +299,7 @@ func (r *Row) SetLoading() {
// SetFailed is shared between the parent struct and the children list. This is // SetFailed is shared between the parent struct and the children list. This is
// because both of those errors share the same appearance, just different // because both of those errors share the same appearance, just different
// callbacks. // callbacks.
func (r *Row) SetFailed(err error, retry func()) { func (r *ServerRow) SetFailed(err error, retry func()) {
AssertUnhollow(r) AssertUnhollow(r)
r.SetSensitive(true) r.SetSensitive(true)
@ -318,7 +310,7 @@ func (r *Row) SetFailed(err error, retry func()) {
// SetDone is shared between the parent struct and the children list. This is // SetDone is shared between the parent struct and the children list. This is
// because both will use the same SetFailed. // because both will use the same SetFailed.
func (r *Row) SetDone() { func (r *ServerRow) SetDone() {
AssertUnhollow(r) AssertUnhollow(r)
r.Button.SetNormal() r.Button.SetNormal()
@ -326,7 +318,7 @@ func (r *Row) SetDone() {
r.SetTooltipText("") r.SetTooltipText("")
} }
func (r *Row) SetNormalExtraMenu(items []menu.Item) { func (r *ServerRow) SetNormalExtraMenu(items []menu.Item) {
AssertUnhollow(r) AssertUnhollow(r)
r.Button.SetNormalExtraMenu(items) r.Button.SetNormalExtraMenu(items)
@ -335,13 +327,13 @@ func (r *Row) SetNormalExtraMenu(items []menu.Item) {
} }
// SetSelected is used for highlighting the current message server. // SetSelected is used for highlighting the current message server.
func (r *Row) SetSelected(selected bool) { func (r *ServerRow) SetSelected(selected bool) {
AssertUnhollow(r) AssertUnhollow(r)
r.Button.SetSelected(selected) r.Button.SetSelected(selected)
} }
func (r *Row) GetActive() bool { func (r *ServerRow) GetActive() bool {
if !r.IsHollow() { if !r.IsHollow() {
return r.Button.GetActive() return r.Button.GetActive()
} }
@ -351,7 +343,7 @@ func (r *Row) GetActive() bool {
// SetRevealChild reveals the list of servers. It does nothing if there are no // SetRevealChild reveals the list of servers. It does nothing if there are no
// servers, meaning if Row does not represent a ServerList. // servers, meaning if Row does not represent a ServerList.
func (r *Row) SetRevealChild(reveal bool) { func (r *ServerRow) SetRevealChild(reveal bool) {
AssertUnhollow(r) AssertUnhollow(r)
// Do the above noop check. // Do the above noop check.
@ -371,11 +363,14 @@ func (r *Row) SetRevealChild(reveal bool) {
// to call Servers on this. Now, we already know that there are hollow // to call Servers on this. Now, we already know that there are hollow
// servers in the children container. // servers in the children container.
r.children.LoadAll() r.children.LoadAll()
// Save the path.
savepath.Update(r, reveal)
} }
// GetRevealChild returns whether or not the server list is expanded, or always // GetRevealChild returns whether or not the server list is expanded, or always
// false if there is no server list. // false if there is no server list.
func (r *Row) GetRevealChild() bool { func (r *ServerRow) GetRevealChild() bool {
AssertUnhollow(r) AssertUnhollow(r)
if r.childrev != nil { if r.childrev != nil {

View File

@ -7,31 +7,76 @@
package traverse package traverse
import ( import (
"strings" "github.com/diamondburned/cchat"
) )
type Breadcrumb []string
func (b Breadcrumb) String() string {
return strings.Join([]string(b), "/")
}
// Breadcrumber is the base interface that other interfaces extend on. A child // Breadcrumber is the base interface that other interfaces extend on. A child
// must at minimum implement this interface to use any other. // must at minimum implement this interface to use any other.
type Breadcrumber interface { type Breadcrumber interface {
// Breadcrumb returns the parent's path before the children's breadcrumb. // Breadcrumb returns the parent's path before the children's breadcrumb.
// This method recursively joins the parent's crumb with the children's, // This method recursively joins the parent's crumb with the children's,
// then eventually make its way up to the root node. // then eventually make its way up to the root node.
Breadcrumb() Breadcrumb ParentBreadcrumb() Breadcrumber
}
type BreadcrumbNamer interface {
// Breadcrumb returns the breadcrumb name.
Breadcrumb() string
}
// Traverse traverses the given breadcrumber recursively. If traverser returns
// true, then the function halts. Traversal is done from parent down to
// children.
func Traverse(bc Breadcrumber, traverser func(b Breadcrumber) bool) {
if bc == nil {
return
}
var stack []Breadcrumber
for current := bc; current != nil; current = current.ParentBreadcrumb() {
stack = append(stack, current)
}
for _, bc := range stack {
if traverser(bc) {
return
}
}
} }
// TryBreadcrumb accepts a nilable breadcrumber and handles it appropriately. // TryBreadcrumb accepts a nilable breadcrumber and handles it appropriately.
func TryBreadcrumb(i Breadcrumber, appended ...string) []string { func TryBreadcrumb(i Breadcrumber) (breadcrumbs []string) {
if i == nil { Traverse(i, func(b Breadcrumber) bool {
return appended if namer, ok := b.(BreadcrumbNamer); ok {
breadcrumbs = append(breadcrumbs, namer.Breadcrumb())
}
return false
})
for l, r := 0, len(breadcrumbs)-1; l < r; l, r = l+1, r-1 {
breadcrumbs[l], breadcrumbs[r] = breadcrumbs[r], breadcrumbs[l]
} }
return append(i.Breadcrumb(), appended...) return
}
func TryID(i Breadcrumber, appended ...cchat.ID) (ids []cchat.ID) {
Traverse(i, func(b Breadcrumber) bool {
switch b := b.(type) {
case cchat.Identifier:
ids = append(ids, b.ID())
case BreadcrumbNamer:
ids = append(ids, b.Breadcrumb())
}
return false
})
for l, r := 0, len(ids)-1; l < r; l, r = l+1, r-1 {
ids[l], ids[r] = ids[r], ids[l]
}
return
} }
// Unreadabler extends Breadcrumber to add unread states to the parent node. // Unreadabler extends Breadcrumber to add unread states to the parent node.

View File

@ -217,8 +217,12 @@ func (r *Row) Reset() {
r.cmder = nil r.cmder = nil
} }
func (r *Row) Breadcrumb() traverse.Breadcrumb { func (r *Row) ParentBreadcrumb() traverse.Breadcrumber {
return traverse.TryBreadcrumb(r.parentcrumb, r.Session.Name().Content) return r.parentcrumb
}
func (r *Row) Breadcrumb() string {
return r.Session.Name().Content
} }
// Activate executes whatever needs to be done. If the row has failed, then this // Activate executes whatever needs to be done. If the row has failed, then this

View File

@ -6,7 +6,6 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui" "github.com/diamondburned/cchat-gtk/internal/ui"
"github.com/diamondburned/cchat-gtk/internal/ui/config" "github.com/diamondburned/cchat-gtk/internal/ui/config"
"github.com/diamondburned/cchat/services" "github.com/diamondburned/cchat/services"
"github.com/pkg/errors"
_ "github.com/diamondburned/cchat-discord" _ "github.com/diamondburned/cchat-discord"
_ "github.com/diamondburned/cchat-mock" _ "github.com/diamondburned/cchat-mock"
@ -33,9 +32,7 @@ func main() {
} }
// Restore the configs. // Restore the configs.
if err := config.Restore(); err != nil { config.Restore()
log.Error(errors.Wrap(err, "Failed to restore config"))
}
return app return app
}) })

View File

@ -1,16 +1,6 @@
{ pkgs ? import <nixpkgs> {} }: { pkgs ? import <nixpkgs> {} }:
# let hunspell = pkgs.hunspellWithDicts(with pkgs.hunspellDicts; [ let libhandy = pkgs.libhandy.overrideAttrs(old: {
# en-us
# en-us-large
# ]);
let hunspellWrapper = pkgs.hunspellWithDicts(with pkgs.hunspellDicts; [
en-us
en-us-large
]);
libhandy = pkgs.libhandy.overrideAttrs(old: {
name = "libhandy-0.90.0"; name = "libhandy-0.90.0";
src = builtins.fetchGit { src = builtins.fetchGit {
url = "https://gitlab.gnome.org/GNOME/libhandy.git"; url = "https://gitlab.gnome.org/GNOME/libhandy.git";
@ -29,8 +19,8 @@ in pkgs.stdenv.mkDerivation rec {
version = "0.0.2"; version = "0.0.2";
buildInputs = buildInputs =
[ libhandy hunspellWrapper ] [ libhandy ]
++ (with pkgs; [ enchant2 gnome3.gspell gnome3.glib gnome3.gtk ]); ++ (with pkgs; [ gnome3.gspell gnome3.glib gnome3.gtk ]);
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
pkgconfig go pkgconfig go