Files
lazyssh/internal/adapters/ui/sort.go
T

131 lines
3.4 KiB
Go

// Copyright 2025.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ui
import (
"sort"
"strings"
"github.com/Adembc/lazyssh/internal/core/domain"
)
// SortMode controls how unpinned servers are ordered in the UI.
type SortMode int
const (
SortByAliasAsc SortMode = iota
SortByAliasDesc
SortByLastSeenDesc
SortByLastSeenAsc
)
func (m SortMode) String() string {
switch m {
case SortByAliasAsc:
return "Alias ↑"
case SortByAliasDesc:
return "Alias ↓"
case SortByLastSeenAsc:
return "Last SSH ↑"
case SortByLastSeenDesc:
return "Last SSH ↓"
default:
return "Alias ↑"
}
}
// ToggleField switches between Alias and LastSeen while preserving direction.
func (m SortMode) ToggleField() SortMode {
switch m {
case SortByAliasAsc:
return SortByLastSeenAsc
case SortByAliasDesc:
return SortByLastSeenDesc
case SortByLastSeenAsc:
return SortByAliasAsc
case SortByLastSeenDesc:
return SortByAliasDesc
default:
return SortByAliasAsc
}
}
// Reverse flips the direction within the current field.
func (m SortMode) Reverse() SortMode {
switch m {
case SortByAliasAsc:
return SortByAliasDesc
case SortByAliasDesc:
return SortByAliasAsc
case SortByLastSeenAsc:
return SortByLastSeenDesc
case SortByLastSeenDesc:
return SortByLastSeenAsc
default:
return SortByAliasAsc
}
}
// sortServersForUI sorts servers according to the rules required by the UI.
// Pinned servers are always at the top, ordered by pinned date (newest first).
// Unpinned servers are sorted by the selected mode. "Never" (zero time) goes to
// the bottom when sorting by last seen asc/desc accordingly. Ties break by Alias asc.
func sortServersForUI(servers []domain.Server, mode SortMode) {
sort.SliceStable(servers, func(i, j int) bool {
si, sj := servers[i], servers[j]
pi, pj := !si.PinnedAt.IsZero(), !sj.PinnedAt.IsZero()
if pi != pj {
return pi
}
if pi && pj { // both pinned: newer pinned first, tie-break alias
if !si.PinnedAt.Equal(sj.PinnedAt) {
return si.PinnedAt.After(sj.PinnedAt)
}
return strings.ToLower(si.Alias) < strings.ToLower(sj.Alias)
}
// both unpinned
switch mode {
case SortByLastSeenDesc, SortByLastSeenAsc:
zi := si.LastSeen.IsZero()
zj := sj.LastSeen.IsZero()
if zi != zj {
// when sorting by last seen, entries with zero (never) should be bottom in either direction
return !zi // non-zero first
}
if !zi && !zj && !si.LastSeen.Equal(sj.LastSeen) {
if mode == SortByLastSeenDesc {
return si.LastSeen.After(sj.LastSeen)
}
return si.LastSeen.Before(sj.LastSeen)
}
// tie-break by alias asc
return strings.ToLower(si.Alias) < strings.ToLower(sj.Alias)
case SortByAliasAsc:
return strings.ToLower(si.Alias) < strings.ToLower(sj.Alias)
case SortByAliasDesc:
ai := strings.ToLower(si.Alias)
aj := strings.ToLower(sj.Alias)
if ai != aj {
return ai > aj
}
return false
default:
return strings.ToLower(si.Alias) < strings.ToLower(sj.Alias)
}
})
}