2024-11-09 00:48:54 +01:00
|
|
|
// 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"
|
|
|
|
)
|
|
|
|
|
2024-11-20 02:20:47 +01:00
|
|
|
const (
|
|
|
|
SimpleId_This = "this"
|
|
|
|
SimpleId_Block = "block"
|
|
|
|
SimpleId_Tab = "tab"
|
|
|
|
SimpleId_Ws = "ws"
|
|
|
|
SimpleId_Workspace = "workspace"
|
|
|
|
SimpleId_Client = "client"
|
|
|
|
SimpleId_Global = "global"
|
2024-11-21 03:48:46 +01:00
|
|
|
SimpleId_Temp = "temp"
|
2024-11-20 02:20:47 +01:00
|
|
|
)
|
2024-11-09 00:48:54 +01:00
|
|
|
|
|
|
|
var (
|
2024-11-09 01:34:50 +01:00
|
|
|
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"
|
2024-11-09 00:48:54 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
2024-11-20 02:20:47 +01:00
|
|
|
if simpleId == SimpleId_This || simpleId == SimpleId_Block || simpleId == SimpleId_Tab ||
|
2024-11-21 03:48:46 +01:00
|
|
|
simpleId == SimpleId_Ws || simpleId == SimpleId_Workspace ||
|
|
|
|
simpleId == SimpleId_Client || simpleId == SimpleId_Global || simpleId == SimpleId_Temp {
|
2024-11-09 00:48:54 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-11-09 01:34:50 +01:00
|
|
|
// check for [view]:N format
|
|
|
|
if viewBlockRe.MatchString(simpleId) {
|
|
|
|
return "view", simpleId, nil
|
|
|
|
}
|
|
|
|
|
2024-11-09 00:48:54 +01:00
|
|
|
// 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")
|
|
|
|
}
|
|
|
|
|
2024-11-20 02:20:47 +01:00
|
|
|
if value == SimpleId_This || value == SimpleId_Block {
|
2024-11-09 00:48:54 +01:00
|
|
|
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
|
|
|
|
}
|
2024-11-20 02:20:47 +01:00
|
|
|
if value == SimpleId_Ws || value == SimpleId_Workspace {
|
2024-11-18 19:27:23 +01:00
|
|
|
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
|
|
|
|
}
|
2024-11-20 02:20:47 +01:00
|
|
|
if value == SimpleId_Client || value == SimpleId_Global {
|
2024-11-18 19:27:23 +01:00
|
|
|
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
|
|
|
|
}
|
2024-11-21 03:48:46 +01:00
|
|
|
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
|
|
|
|
}
|
2024-11-09 00:48:54 +01:00
|
|
|
return nil, fmt.Errorf("invalid value for 'this' resolver: %s", value)
|
|
|
|
}
|
|
|
|
|
2024-11-15 01:20:33 +01:00
|
|
|
func resolveORef(_ context.Context, value string) (*waveobj.ORef, error) {
|
2024-11-09 00:48:54 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-12-11 21:52:15 +01:00
|
|
|
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)
|
2024-11-09 00:48:54 +01:00
|
|
|
}
|
|
|
|
|
2024-12-11 21:52:15 +01:00
|
|
|
tabIdx := tabNum - 1
|
|
|
|
var resolvedTabId string
|
|
|
|
if tabIdx < numPinnedTabs {
|
|
|
|
resolvedTabId = ws.PinnedTabIds[tabIdx]
|
|
|
|
} else {
|
|
|
|
resolvedTabId = ws.TabIds[tabIdx-numPinnedTabs]
|
|
|
|
}
|
2024-11-09 00:48:54 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-11-09 01:34:50 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-11-09 00:48:54 +01:00
|
|
|
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)
|
2024-11-09 01:34:50 +01:00
|
|
|
case "view":
|
|
|
|
return resolveView(ctx, data, value)
|
2024-11-09 00:48:54 +01:00
|
|
|
case "uuid", "uuid8":
|
|
|
|
return resolveUUID(ctx, value)
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unknown discriminator: %s", discriminator)
|
|
|
|
}
|
|
|
|
}
|