xterm
[VSoRC/.git] / node_modules / xterm / src / common / parser / OscParser.ts
1 /**
2  * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3  * @license MIT
4  */
5
6 import { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser } from 'common/parser/Types';
7 import { OscState, PAYLOAD_LIMIT } from 'common/parser/Constants';
8 import { utf32ToString } from 'common/input/TextDecoder';
9 import { IDisposable } from 'common/Types';
10
11
12 export class OscParser implements IOscParser {
13   private _state = OscState.START;
14   private _id = -1;
15   private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);
16   private _handlerFb: OscFallbackHandlerType = () => { };
17
18   public addHandler(ident: number, handler: IOscHandler): IDisposable {
19     if (this._handlers[ident] === undefined) {
20       this._handlers[ident] = [];
21     }
22     const handlerList = this._handlers[ident];
23     handlerList.push(handler);
24     return {
25       dispose: () => {
26         const handlerIndex = handlerList.indexOf(handler);
27         if (handlerIndex !== -1) {
28           handlerList.splice(handlerIndex, 1);
29         }
30       }
31     };
32   }
33   public setHandler(ident: number, handler: IOscHandler): void {
34     this._handlers[ident] = [handler];
35   }
36   public clearHandler(ident: number): void {
37     if (this._handlers[ident]) delete this._handlers[ident];
38   }
39   public setHandlerFallback(handler: OscFallbackHandlerType): void {
40     this._handlerFb = handler;
41   }
42
43   public dispose(): void {
44     this._handlers = Object.create(null);
45     this._handlerFb = () => {};
46   }
47
48   public reset(): void {
49     // cleanup handlers if payload was already sent
50     if (this._state === OscState.PAYLOAD) {
51       this.end(false);
52     }
53     this._id = -1;
54     this._state = OscState.START;
55   }
56
57   private _start(): void {
58     const handlers = this._handlers[this._id];
59     if (!handlers) {
60       this._handlerFb(this._id, 'START');
61     } else {
62       for (let j = handlers.length - 1; j >= 0; j--) {
63         handlers[j].start();
64       }
65     }
66   }
67
68   private _put(data: Uint32Array, start: number, end: number): void {
69     const handlers = this._handlers[this._id];
70     if (!handlers) {
71       this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end));
72     } else {
73       for (let j = handlers.length - 1; j >= 0; j--) {
74         handlers[j].put(data, start, end);
75       }
76     }
77   }
78
79   private _end(success: boolean): void {
80     // other than the old code we always have to call .end
81     // to keep the bubbling we use `success` to indicate
82     // whether a handler should execute
83     const handlers = this._handlers[this._id];
84     if (!handlers) {
85       this._handlerFb(this._id, 'END', success);
86     } else {
87       let j = handlers.length - 1;
88       for (; j >= 0; j--) {
89         if (handlers[j].end(success) !== false) {
90           break;
91         }
92       }
93       j--;
94       // cleanup left over handlers
95       for (; j >= 0; j--) {
96         handlers[j].end(false);
97       }
98     }
99   }
100
101   public start(): void {
102     // always reset leftover handlers
103     this.reset();
104     this._id = -1;
105     this._state = OscState.ID;
106   }
107
108   /**
109    * Put data to current OSC command.
110    * Expects the identifier of the OSC command in the form
111    * OSC id ; payload ST/BEL
112    * Payload chunks are not further processed and get
113    * directly passed to the handlers.
114    */
115   public put(data: Uint32Array, start: number, end: number): void {
116     if (this._state === OscState.ABORT) {
117       return;
118     }
119     if (this._state === OscState.ID) {
120       while (start < end) {
121         const code = data[start++];
122         if (code === 0x3b) {
123           this._state = OscState.PAYLOAD;
124           this._start();
125           break;
126         }
127         if (code < 0x30 || 0x39 < code) {
128           this._state = OscState.ABORT;
129           return;
130         }
131         if (this._id === -1) {
132           this._id = 0;
133         }
134         this._id = this._id * 10 + code - 48;
135       }
136     }
137     if (this._state === OscState.PAYLOAD && end - start > 0) {
138       this._put(data, start, end);
139     }
140   }
141
142   /**
143    * Indicates end of an OSC command.
144    * Whether the OSC got aborted or finished normally
145    * is indicated by `success`.
146    */
147   public end(success: boolean): void {
148     if (this._state === OscState.START) {
149       return;
150     }
151     // do nothing if command was faulty
152     if (this._state !== OscState.ABORT) {
153       // if we are still in ID state and get an early end
154       // means that the command has no payload thus we still have
155       // to announce START and send END right after
156       if (this._state === OscState.ID) {
157         this._start();
158       }
159       this._end(success);
160     }
161     this._id = -1;
162     this._state = OscState.START;
163   }
164 }
165
166 /**
167  * Convenient class to allow attaching string based handler functions
168  * as OSC handlers.
169  */
170 export class OscHandler implements IOscHandler {
171   private _data = '';
172   private _hitLimit: boolean = false;
173
174   constructor(private _handler: (data: string) => any) {}
175
176   public start(): void {
177     this._data = '';
178     this._hitLimit = false;
179   }
180
181   public put(data: Uint32Array, start: number, end: number): void {
182     if (this._hitLimit) {
183       return;
184     }
185     this._data += utf32ToString(data, start, end);
186     if (this._data.length > PAYLOAD_LIMIT) {
187       this._data = '';
188       this._hitLimit = true;
189     }
190   }
191
192   public end(success: boolean): any {
193     let ret;
194     if (this._hitLimit) {
195       ret = false;
196     } else if (success) {
197       ret = this._handler(this._data);
198     }
199     this._data = '';
200     this._hitLimit = false;
201     return ret;
202   }
203 }