waveterm/pkg/wshrpc/wshserver/resolvers.go
Evan Simkowitz 4070abadde
Squash some leftover bugs (#1495)
Only create new tab in `CheckAndFixWindow` if no tabs or pinned tabs
exist

Update `resolvers.resolveTabNum` to account for pinned tabs

Remove obsolete and unused `wstore.DeleteTab`

Only show accelerators for first 9 workspaces in workspace app menu to
be consistent with other keybindings

Fix tabbar spacing to remove min size for drag right spacer, account for
workspace switcher button size

Fix updatebanner size calculations
2024-12-11 12:52:15 -08:00

289 lines
8.6 KiB
Go

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wshserver
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/google/uuid"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/pkg/wstore"
)
const (
SimpleId_This = "this"
SimpleId_Block = "block"
SimpleId_Tab = "tab"
SimpleId_Ws = "ws"
SimpleId_Workspace = "workspace"
SimpleId_Client = "client"
SimpleId_Global = "global"
SimpleId_Temp = "temp"
)
var (
simpleTabNumRe = regexp.MustCompile(`^tab:(\d{1,3})$`)
shortUUIDRe = regexp.MustCompile(`^[0-9a-f]{8}$`)
viewBlockRe = regexp.MustCompile(`^([a-z]+)(?::(\d+))?$`) // Matches "ai" or "ai:2"
)
// First function: detect/choose discriminator
func parseSimpleId(simpleId string) (discriminator string, value string, err error) {
// Check for explicit discriminator with @
if parts := strings.SplitN(simpleId, "@", 2); len(parts) == 2 {
return parts[0], parts[1], nil
}
// Handle special keywords
if simpleId == SimpleId_This || simpleId == SimpleId_Block || simpleId == SimpleId_Tab ||
simpleId == SimpleId_Ws || simpleId == SimpleId_Workspace ||
simpleId == SimpleId_Client || simpleId == SimpleId_Global || simpleId == SimpleId_Temp {
return "this", simpleId, nil
}
// Check if it's a simple ORef (type:uuid)
if _, err := waveobj.ParseORef(simpleId); err == nil {
return "oref", simpleId, nil
}
// Check for tab:N format
if simpleTabNumRe.MatchString(simpleId) {
return "tabnum", simpleId, nil
}
// check for [view]:N format
if viewBlockRe.MatchString(simpleId) {
return "view", simpleId, nil
}
// Check for plain number (block reference)
if _, err := strconv.Atoi(simpleId); err == nil {
return "blocknum", simpleId, nil
}
// Check for UUIDs
if _, err := uuid.Parse(simpleId); err == nil {
return "uuid", simpleId, nil
}
if shortUUIDRe.MatchString(strings.ToLower(simpleId)) {
return "uuid8", simpleId, nil
}
return "", "", fmt.Errorf("invalid simple id format: %s", simpleId)
}
// Individual resolvers
func resolveThis(ctx context.Context, data wshrpc.CommandResolveIdsData, value string) (*waveobj.ORef, error) {
if data.BlockId == "" {
return nil, fmt.Errorf("no blockid in request")
}
if value == SimpleId_This || value == SimpleId_Block {
return &waveobj.ORef{OType: waveobj.OType_Block, OID: data.BlockId}, nil
}
if value == SimpleId_Tab {
tabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
if err != nil {
return nil, fmt.Errorf("error finding tab: %v", err)
}
return &waveobj.ORef{OType: waveobj.OType_Tab, OID: tabId}, nil
}
if value == SimpleId_Ws || value == SimpleId_Workspace {
tabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
if err != nil {
return nil, fmt.Errorf("error finding tab: %v", err)
}
wsId, err := wstore.DBFindWorkspaceForTabId(ctx, tabId)
if err != nil {
return nil, fmt.Errorf("error finding workspace: %v", err)
}
return &waveobj.ORef{OType: waveobj.OType_Workspace, OID: wsId}, nil
}
if value == SimpleId_Client || value == SimpleId_Global {
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
if err != nil {
return nil, fmt.Errorf("error getting client: %v", err)
}
return &waveobj.ORef{OType: waveobj.OType_Client, OID: client.OID}, nil
}
if value == SimpleId_Temp {
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
if err != nil {
return nil, fmt.Errorf("error getting client: %v", err)
}
return &waveobj.ORef{OType: "temp", OID: client.TempOID}, nil
}
return nil, fmt.Errorf("invalid value for 'this' resolver: %s", value)
}
func resolveORef(_ context.Context, value string) (*waveobj.ORef, error) {
parsedORef, err := waveobj.ParseORef(value)
if err != nil {
return nil, fmt.Errorf("error parsing oref: %v", err)
}
return &parsedORef, nil
}
func resolveTabNum(ctx context.Context, data wshrpc.CommandResolveIdsData, value string) (*waveobj.ORef, error) {
m := simpleTabNumRe.FindStringSubmatch(value)
if m == nil {
return nil, fmt.Errorf("error parsing simple tab id: %s", value)
}
tabNum, err := strconv.Atoi(m[1])
if err != nil {
return nil, fmt.Errorf("error parsing simple tab num: %v", err)
}
curTabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
if err != nil {
return nil, fmt.Errorf("error finding tab for block: %v", err)
}
wsId, err := wstore.DBFindWorkspaceForTabId(ctx, curTabId)
if err != nil {
return nil, fmt.Errorf("error finding current workspace: %v", err)
}
ws, err := wstore.DBMustGet[*waveobj.Workspace](ctx, wsId)
if err != nil {
return nil, fmt.Errorf("error getting workspace: %v", err)
}
numPinnedTabs := len(ws.PinnedTabIds)
numTabs := len(ws.TabIds) + numPinnedTabs
if tabNum < 1 || tabNum > numTabs {
return nil, fmt.Errorf("tab num out of range, workspace has %d tabs", numTabs)
}
tabIdx := tabNum - 1
var resolvedTabId string
if tabIdx < numPinnedTabs {
resolvedTabId = ws.PinnedTabIds[tabIdx]
} else {
resolvedTabId = ws.TabIds[tabIdx-numPinnedTabs]
}
return &waveobj.ORef{OType: waveobj.OType_Tab, OID: resolvedTabId}, nil
}
func resolveBlock(ctx context.Context, data wshrpc.CommandResolveIdsData, value string) (*waveobj.ORef, error) {
blockNum, err := strconv.Atoi(value)
if err != nil {
return nil, fmt.Errorf("error parsing block number: %v", err)
}
tabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
if err != nil {
return nil, fmt.Errorf("error finding tab for blockid %s: %w", data.BlockId, err)
}
tab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)
if err != nil {
return nil, fmt.Errorf("error retrieving tab %s: %w", tabId, err)
}
layout, err := wstore.DBGet[*waveobj.LayoutState](ctx, tab.LayoutState)
if err != nil {
return nil, fmt.Errorf("error retrieving layout state %s: %w", tab.LayoutState, err)
}
if layout.LeafOrder == nil {
return nil, fmt.Errorf("could not resolve block num %v, leaf order is empty", blockNum)
}
leafIndex := blockNum - 1 // block nums are 1-indexed
if len(*layout.LeafOrder) <= leafIndex {
return nil, fmt.Errorf("could not find a node in the layout matching blockNum %v", blockNum)
}
leafEntry := (*layout.LeafOrder)[leafIndex]
return &waveobj.ORef{OType: waveobj.OType_Block, OID: leafEntry.BlockId}, nil
}
func resolveView(ctx context.Context, data wshrpc.CommandResolveIdsData, value string) (*waveobj.ORef, error) {
matches := viewBlockRe.FindStringSubmatch(value)
if matches == nil {
return nil, fmt.Errorf("invalid view format: %s", value)
}
// Default to first instance if no number specified
viewType := matches[1]
instanceNum := 1
if matches[2] != "" {
num, err := strconv.Atoi(matches[2])
if err != nil {
return nil, fmt.Errorf("invalid view instance number: %v", err)
}
instanceNum = num
}
if instanceNum < 1 {
return nil, fmt.Errorf("invalid view instance number: %d", instanceNum)
}
// Get current tab
tabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
if err != nil {
return nil, fmt.Errorf("error finding tab: %v", err)
}
tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId)
if err != nil {
return nil, fmt.Errorf("error retrieving tab: %v", err)
}
layout, err := wstore.DBMustGet[*waveobj.LayoutState](ctx, tab.LayoutState)
if err != nil {
return nil, fmt.Errorf("error retrieving layout: %v", err)
}
if layout.LeafOrder == nil {
return nil, fmt.Errorf("no blocks in layout")
}
// Find nth instance of view type
count := 0
for _, leaf := range *layout.LeafOrder {
leafBlockId := leaf.BlockId
leafBlock, err := wstore.DBMustGet[*waveobj.Block](ctx, leafBlockId)
if err != nil {
continue
}
if leafBlock.Meta.GetString("view", "") == viewType {
count++
if count == instanceNum {
return &waveobj.ORef{OType: waveobj.OType_Block, OID: leaf.BlockId}, nil
}
}
}
return nil, fmt.Errorf("could not find block %d of type %s (found %d)", instanceNum, viewType, count)
}
func resolveUUID(ctx context.Context, value string) (*waveobj.ORef, error) {
return wstore.DBResolveEasyOID(ctx, value)
}
// Main resolver function
func resolveSimpleId(ctx context.Context, data wshrpc.CommandResolveIdsData, simpleId string) (*waveobj.ORef, error) {
discriminator, value, err := parseSimpleId(simpleId)
if err != nil {
return nil, err
}
switch discriminator {
case "this":
return resolveThis(ctx, data, value)
case "oref":
return resolveORef(ctx, value)
case "tabnum":
return resolveTabNum(ctx, data, value)
case "blocknum":
return resolveBlock(ctx, data, value)
case "view":
return resolveView(ctx, data, value)
case "uuid", "uuid8":
return resolveUUID(ctx, value)
default:
return nil, fmt.Errorf("unknown discriminator: %s", discriminator)
}
}