aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/msglist.go17
-rw-r--r--app/scrollable.go24
-rw-r--r--commands/account/align.go60
-rw-r--r--config/binds.conf4
-rw-r--r--doc/aerc.1.scd7
5 files changed, 112 insertions, 0 deletions
diff --git a/app/msglist.go b/app/msglist.go
index a5e5fbb6..38970123 100644
--- a/app/msglist.go
+++ b/app/msglist.go
@@ -51,6 +51,23 @@ type messageRowParams struct {
headers *mail.Header
}
+// AlignMessage aligns the selected message to position pos.
+func (ml *MessageList) AlignMessage(pos AlignPosition) {
+ store := ml.Store()
+ if store == nil {
+ return
+ }
+ idx := 0
+ iter := store.UidsIterator()
+ for i := 0; iter.Next(); i++ {
+ if store.SelectedUid() == iter.Value().(uint32) {
+ idx = i
+ break
+ }
+ }
+ ml.Align(idx, pos)
+}
+
func (ml *MessageList) Draw(ctx *ui.Context) {
ml.height = ctx.Height()
ml.width = ctx.Width()
diff --git a/app/scrollable.go b/app/scrollable.go
index 3a0555fe..04436375 100644
--- a/app/scrollable.go
+++ b/app/scrollable.go
@@ -62,6 +62,10 @@ func (s *Scrollable) EnsureScroll(idx int) {
s.scroll = idx + s.offset - s.height + 1
}
+ s.checkBounds()
+}
+
+func (s *Scrollable) checkBounds() {
maxScroll := s.elems - s.height
if maxScroll < 0 {
maxScroll = 0
@@ -75,3 +79,23 @@ func (s *Scrollable) EnsureScroll(idx int) {
s.scroll = 0
}
}
+
+type AlignPosition uint
+
+const (
+ AlignTop AlignPosition = iota
+ AlignCenter
+ AlignBottom
+)
+
+func (s *Scrollable) Align(idx int, pos AlignPosition) {
+ switch pos {
+ case AlignTop:
+ s.scroll = idx
+ case AlignCenter:
+ s.scroll = idx - s.height/2
+ case AlignBottom:
+ s.scroll = idx - s.height + 1
+ }
+ s.checkBounds()
+}
diff --git a/commands/account/align.go b/commands/account/align.go
new file mode 100644
index 00000000..7f835b71
--- /dev/null
+++ b/commands/account/align.go
@@ -0,0 +1,60 @@
+package account
+
+import (
+ "errors"
+
+ "git.sr.ht/~rjarry/aerc/app"
+ "git.sr.ht/~rjarry/aerc/commands"
+ "git.sr.ht/~rjarry/aerc/lib/ui"
+)
+
+type Align struct {
+ Pos app.AlignPosition `opt:"pos" metavar:"top|center|bottom" action:"ParsePos" complete:"CompletePos"`
+}
+
+func init() {
+ commands.Register(Align{})
+}
+
+var posNames []string = []string{"top", "center", "bottom"}
+
+func (a *Align) ParsePos(arg string) error {
+ switch arg {
+ case "top":
+ a.Pos = app.AlignTop
+ case "center":
+ a.Pos = app.AlignCenter
+ case "bottom":
+ a.Pos = app.AlignBottom
+ default:
+ return errors.New("invalid alignment")
+ }
+ return nil
+}
+
+func (a *Align) CompletePos(arg string) []string {
+ return commands.FilterList(posNames, arg, commands.QuoteSpace)
+}
+
+func (Align) Context() commands.CommandContext {
+ return commands.ACCOUNT
+}
+
+func (Align) Aliases() []string {
+ return []string{"align"}
+}
+
+func (a Align) Execute(args []string) error {
+ acct := app.SelectedAccount()
+ if acct == nil {
+ return errors.New("no account selected")
+ }
+ msgList := acct.Messages()
+ if msgList == nil {
+ return errors.New("no message list available")
+ }
+ msgList.AlignMessage(a.Pos)
+ ui.Invalidate()
+
+ return nil
+}
diff --git a/config/binds.conf b/config/binds.conf
index 011d86ee..125a906b 100644
--- a/config/binds.conf
+++ b/config/binds.conf
@@ -51,6 +51,10 @@ zM = :fold -a<Enter>
zR = :unfold -a<Enter>
<tab> = :fold -t<Enter>
+zz = :align center<Enter>
+zt = :align top<Enter>
+zb = :align bottom<Enter>
+
<Enter> = :view<Enter>
d = :prompt 'Really delete this message?' 'delete-message'<Enter>
D = :delete<Enter>
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 74cb0dd3..e3079090 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -452,6 +452,13 @@ message list, the message in the message viewer, etc).
## MESSAGE LIST COMMANDS
+*:align* _top|center|bottom_
+ Aligns the selected message. The available positions are:
+
+ _top_: Top of the message list.++
+ _center_: Center of the message list.++
+ _bottom_: Bottom of the message list.
+
*:disconnect*++
*:connect*
Disconnect or reconnect the current account. This only applies to