mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
4070abadde
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
289 lines
8.6 KiB
Go
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)
|
|
}
|
|
}
|