github.com/ericwq/aprilsh@v0.0.0-20240517091432-958bc568daa0/statesync/user.go (about)

     1  // Copyright 2022 wangqi. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package statesync
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"reflect"
    11  	"strings"
    12  
    13  	pb "github.com/ericwq/aprilsh/protobufs/user"
    14  	"github.com/ericwq/aprilsh/terminal"
    15  	"github.com/rivo/uniseg"
    16  	"google.golang.org/protobuf/proto"
    17  )
    18  
    19  type UserEventType uint8
    20  
    21  const (
    22  	UserByteType UserEventType = iota
    23  	ResizeType
    24  )
    25  
    26  // UserByte instance is created by aprish client, when client got the user input.
    27  // Resize instance is created by aprish client, when client change the window size.
    28  type UserEvent struct {
    29  	theType  UserEventType
    30  	userByte terminal.UserByte // Parser::UserByte
    31  	resize   terminal.Resize   // Parser::Resize
    32  }
    33  
    34  func NewUserEvent(userByte terminal.UserByte) (u UserEvent) {
    35  	u = UserEvent{}
    36  
    37  	u.theType = UserByteType
    38  	u.userByte = userByte
    39  
    40  	return u
    41  }
    42  
    43  func NewUserEventResize(resize terminal.Resize) (u UserEvent) {
    44  	u = UserEvent{}
    45  
    46  	u.theType = ResizeType
    47  	u.resize = resize
    48  
    49  	return u
    50  }
    51  
    52  // UserStream implements network.State[C any] interface
    53  type UserStream struct {
    54  	actions []UserEvent
    55  }
    56  
    57  func (u *UserStream) String() string {
    58  	var output1 strings.Builder
    59  	var output2 strings.Builder
    60  
    61  	for _, v := range u.actions {
    62  		switch v.theType {
    63  		case UserByteType:
    64  			// output1.WriteRune(v.userByte.C)
    65  			output1.WriteString(string(v.userByte.Chs))
    66  		case ResizeType:
    67  			output2.WriteString(fmt.Sprintf("(%d,%d),", v.resize.Width, v.resize.Height))
    68  		}
    69  	}
    70  
    71  	return fmt.Sprintf("Keystroke:%q, Resize:%s, size=%d", output1.String(), output2.String(), len(u.actions))
    72  }
    73  
    74  func (u *UserStream) PushBack(x []rune) {
    75  	userStroke := terminal.UserByte{Chs: x}
    76  	u.actions = append(u.actions, NewUserEvent(userStroke))
    77  }
    78  
    79  func (u *UserStream) PushBackResize(width, height int) {
    80  	resize := terminal.Resize{Width: width, Height: height}
    81  	u.actions = append(u.actions, NewUserEventResize(resize))
    82  }
    83  
    84  func (u *UserStream) Empty() bool {
    85  	return len(u.actions) == 0
    86  }
    87  
    88  func (u *UserStream) Size() int {
    89  	return len(u.actions)
    90  }
    91  
    92  func (u *UserStream) GetAction(i int) terminal.ActOn {
    93  	if 0 <= i && i < len(u.actions) {
    94  		switch u.actions[i].theType {
    95  		case UserByteType:
    96  			return u.actions[i].userByte
    97  		case ResizeType:
    98  			return u.actions[i].resize
    99  		}
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // implements network.State[C any] interface
   106  // Subtract() the prefix UserStream from current UserStream
   107  func (u *UserStream) Subtract(prefix *UserStream) {
   108  	// fmt.Printf("#Subtract %q %p from %q %p\n", prefix, prefix, u, u)
   109  	// if we are subtracting ourself from ourself, just clear the deque
   110  	if u.Equal(prefix) {
   111  		u.actions = make([]UserEvent, 0)
   112  		return
   113  	}
   114  
   115  	for i := range prefix.actions {
   116  		// fmt.Printf("#Subtract compare %q[0] vs %q[%d]\n", u.actions[0], prefix.actions[i], i)
   117  		if len(u.actions) > 0 && reflect.DeepEqual(u.actions[0], prefix.actions[i]) {
   118  			// fmt.Printf("#Subtract equal %d %q\n", i, prefix.actions[i])
   119  			u.actions = u.actions[1:]
   120  		} else {
   121  			// fmt.Printf("#Subtract save %d %q\n", i, prefix.actions[i])
   122  			break
   123  		}
   124  	}
   125  }
   126  
   127  // implements network.State[C any] interface
   128  // DiffFrom() exclude the existing UserEvent and return the difference.
   129  func (u *UserStream) DiffFrom(existing *UserStream) string {
   130  	// skip the existing part
   131  	pos := 0
   132  	for i := range existing.actions {
   133  		if len(u.actions[pos:]) > 0 && reflect.DeepEqual(u.actions[pos], existing.actions[i]) {
   134  			pos++
   135  		} else {
   136  			break
   137  		}
   138  	}
   139  
   140  	// create the UserMessage based on content in UserStream
   141  	um := pb.UserMessage{}
   142  	for _, ue := range u.actions[pos:] {
   143  		switch ue.theType {
   144  		case UserByteType:
   145  			idx := len(um.Instruction) - 1 // TODO the last one?
   146  			var buf bytes.Buffer
   147  			buf.WriteString(string(ue.userByte.Chs))
   148  			keys := buf.Bytes()
   149  
   150  			if len(um.Instruction) > 0 && um.Instruction[idx].Keystroke != nil {
   151  				// append Keys for Keystroke
   152  
   153  				um.Instruction[idx].Keystroke.Keys = append(um.Instruction[idx].Keystroke.Keys, keys...)
   154  			} else {
   155  				// create a new Instruction for Keystroke
   156  				um.Instruction = make([]*pb.Instruction, 0)
   157  
   158  				inst := pb.Instruction{
   159  					Keystroke: &pb.Keystroke{Keys: keys},
   160  				}
   161  				um.Instruction = append(um.Instruction, &inst)
   162  			}
   163  		case ResizeType:
   164  			// create a new Instruction for ResizeMessage
   165  			inst := pb.Instruction{
   166  				Resize: &pb.ResizeMessage{Width: int32(ue.resize.Width), Height: int32(ue.resize.Height)},
   167  			}
   168  			um.Instruction = append(um.Instruction, &inst)
   169  		}
   170  	}
   171  
   172  	// get the wire-format encoding of UserMessage
   173  	output, _ := proto.Marshal(&um)
   174  	// if err != nil {
   175  	// 	panic(fmt.Sprintf("#DiffFrom marshal %s ", err))
   176  	// }
   177  
   178  	return string(output)
   179  }
   180  
   181  // implements network.State[C any] interface
   182  func (u *UserStream) InitDiff() string {
   183  	return ""
   184  }
   185  
   186  // implements network.State[C any] interface
   187  // convert the UserMessage into a UserStream
   188  func (u *UserStream) ApplyString(diff string) error {
   189  	// parse the wire-format encoding of UserMessage
   190  	input := pb.UserMessage{}
   191  	err := proto.Unmarshal([]byte(diff), &input)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	// create the UserStream based on content of UserMessage
   197  	for i := range input.Instruction {
   198  		if input.Instruction[i].Keystroke != nil {
   199  			graphemes := uniseg.NewGraphemes(string(input.Instruction[i].Keystroke.Keys))
   200  
   201  			for graphemes.Next() {
   202  				chs := graphemes.Runes()
   203  				u.actions = append(u.actions, NewUserEvent(terminal.UserByte{Chs: chs}))
   204  			}
   205  		} else if input.Instruction[i].Resize != nil {
   206  			w := input.Instruction[i].Resize
   207  			u.actions = append(u.actions, NewUserEventResize(terminal.Resize{Width: int(w.Width), Height: int(w.Height)}))
   208  		}
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  // implements network.State[C any] interface
   215  func (u *UserStream) Equal(x *UserStream) bool {
   216  	if u == x || (len(u.actions) == 0 && len(x.actions) == 0) {
   217  		return true
   218  	}
   219  	return reflect.DeepEqual(u.actions, x.actions)
   220  }
   221  
   222  // implements network.State[C any] interface
   223  func (u *UserStream) ResetInput()               {}
   224  func (u *UserStream) Reset()                    {}
   225  func (u *UserStream) InitSize(nCols, nRows int) {}
   226  
   227  // implements network.State[C any] interface
   228  func (u *UserStream) Clone() *UserStream {
   229  	clone := UserStream{}
   230  	clone.actions = make([]UserEvent, len(u.actions))
   231  
   232  	for i := range u.actions { // actions slice
   233  		if u.actions[i].theType == UserByteType { // clone UserByte
   234  			chs := make([]rune, len(u.actions[i].userByte.Chs))
   235  			copy(chs, u.actions[i].userByte.Chs)
   236  
   237  			clone.actions[i].userByte = terminal.UserByte{}
   238  			clone.actions[i].userByte.Chs = chs
   239  		} else {
   240  			clone.actions[i] = u.actions[i] // clone Resize
   241  		}
   242  	}
   243  
   244  	return &clone
   245  }
   246  
   247  // for test purpose
   248  func (u *UserStream) EqualTrace(x *UserStream) bool {
   249  	return u.Equal(x)
   250  }