@@ -4,6 +4,7 @@ import {FitAddon} from 'xterm-addon-fit';
44import { WebLinksAddon } from 'xterm-addon-web-links' ;
55import { SearchAddon , ISearchDecorationOptions } from 'xterm-addon-search' ;
66import { WebglAddon } from 'xterm-addon-webgl' ;
7+ import { CanvasAddon } from 'xterm-addon-canvas' ;
78import { LigaturesAddon } from 'xterm-addon-ligatures' ;
89import { Unicode11Addon } from 'xterm-addon-unicode11' ;
910import { clipboard , shell } from 'electron' ;
@@ -12,7 +13,7 @@ import terms from '../terms';
1213import processClipboard from '../utils/paste' ;
1314import _SearchBox from './searchBox' ;
1415import { TermProps } from '../hyper' ;
15- import { ObjectTypedKeys } from '../utils/object ' ;
16+ import { pickBy , isEqual } from 'lodash ' ;
1617import { decorate } from '../utils/plugins' ;
1718import 'xterm/css/xterm.css' ;
1819
@@ -57,14 +58,13 @@ const getTermOptions = (props: TermProps): ITerminalOptions => {
5758 letterSpacing : props . letterSpacing ,
5859 allowTransparency : needTransparency ,
5960 macOptionClickForcesSelection : props . macOptionSelectionMode === 'force' ,
60- bellStyle : props . bell === 'SOUND' ? 'sound' : 'none' ,
6161 windowsMode : isWindows ,
6262 theme : {
6363 foreground : props . foregroundColor ,
6464 background : backgroundColor ,
6565 cursor : props . cursorColor ,
6666 cursorAccent : props . cursorAccentColor ,
67- selection : props . selectionColor ,
67+ selectionBackground : props . selectionColor ,
6868 black : props . colors . black ,
6969 red : props . colors . red ,
7070 green : props . colors . green ,
@@ -83,7 +83,8 @@ const getTermOptions = (props: TermProps): ITerminalOptions => {
8383 brightWhite : props . colors . lightWhite
8484 } ,
8585 screenReaderMode : props . screenReaderMode ,
86- overviewRulerWidth : 20
86+ overviewRulerWidth : 20 ,
87+ allowProposedApi : true
8788 } ;
8889} ;
8990
@@ -107,7 +108,8 @@ export default class Term extends React.PureComponent<
107108 termWrapperRef : HTMLElement | null ;
108109 termOptions : ITerminalOptions ;
109110 disposableListeners : IDisposable [ ] ;
110- termDefaultBellSound : string | null ;
111+ defaultBellSound : HTMLAudioElement | null ;
112+ bellSound : HTMLAudioElement | null ;
111113 fitAddon : FitAddon ;
112114 searchAddon : SearchAddon ;
113115 static rendererTypes : Record < string , string > ;
@@ -131,7 +133,8 @@ export default class Term extends React.PureComponent<
131133 this . termWrapperRef = null ;
132134 this . termOptions = { } ;
133135 this . disposableListeners = [ ] ;
134- this . termDefaultBellSound = null ;
136+ this . defaultBellSound = null ;
137+ this . bellSound = null ;
135138 this . fitAddon = new FitAddon ( ) ;
136139 this . searchAddon = new SearchAddon ( ) ;
137140 this . searchDecorations = {
@@ -158,7 +161,14 @@ export default class Term extends React.PureComponent<
158161
159162 this . termOptions = getTermOptions ( props ) ;
160163 this . term = props . term || new Terminal ( this . termOptions ) ;
161- this . termDefaultBellSound = this . term . getOption ( 'bellSound' ) ;
164+ this . defaultBellSound = new Audio (
165+ // Source: https://freesound.org/people/altemark/sounds/45759/
166+ // This sound is released under the Creative Commons Attribution 3.0 Unported
167+ // (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
168+ // made, apart from the conversion to base64.
169+ 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'
170+ ) ;
171+ this . setBellSound ( props . bell , props . bellSound ) ;
162172
163173 // The parent element for the terminal is attached and removed manually so
164174 // that we can preserve it across mounts and unmounts of the component
@@ -186,35 +196,27 @@ export default class Term extends React.PureComponent<
186196 }
187197 Term . reportRenderer ( props . uid , useWebGL ? 'WebGL' : 'Canvas' ) ;
188198
189- const shallActivateWebLink = ( event : Record < string , any > | undefined ) : boolean => {
190- // eslint-disable-next-line @typescript-eslint/no-unsafe- return
191- return event && ( ! props . webLinksActivationKey || event [ `${ props . webLinksActivationKey } Key` ] ) ;
199+ const shallActivateWebLink = ( event : MouseEvent ) : boolean => {
200+ if ( ! event ) return false ;
201+ return props . webLinksActivationKey ? event [ `${ props . webLinksActivationKey } Key` ] : true ;
192202 } ;
193203
194204 // eslint-disable-next-line @typescript-eslint/unbound-method
195205 this . term . attachCustomKeyEventHandler ( this . keyboardHandler ) ;
196206 this . term . loadAddon ( this . fitAddon ) ;
197207 this . term . loadAddon ( this . searchAddon ) ;
198208 this . term . loadAddon (
199- new WebLinksAddon (
200- ( event : MouseEvent | undefined , uri : string ) => {
201- if ( shallActivateWebLink ( event ) ) void shell . openExternal ( uri ) ;
202- } ,
203- {
204- // prevent default electron link handling to allow selection, e.g. via double-click
205- willLinkActivate : ( event : MouseEvent | undefined ) => {
206- event ?. preventDefault ( ) ;
207- return shallActivateWebLink ( event ) ;
208- } ,
209- priority : Date . now ( )
210- }
211- )
209+ new WebLinksAddon ( ( event , uri ) => {
210+ if ( shallActivateWebLink ( event ) ) void shell . openExternal ( uri ) ;
211+ } )
212212 ) ;
213213 this . term . open ( this . termRef ) ;
214214 if ( useWebGL ) {
215215 this . term . loadAddon ( new WebglAddon ( ) ) ;
216+ } else {
217+ this . term . loadAddon ( new CanvasAddon ( ) ) ;
216218 }
217- if ( props . disableLigatures !== true && ! useWebGL ) {
219+ if ( props . disableLigatures !== true ) {
218220 this . term . loadAddon ( new LigaturesAddon ( ) ) ;
219221 }
220222 this . term . loadAddon ( new Unicode11Addon ( ) ) ;
@@ -252,6 +254,10 @@ export default class Term extends React.PureComponent<
252254 this . disposableListeners . push ( this . term . onData ( props . onData ) ) ;
253255 }
254256
257+ this . term . onBell ( ( ) => {
258+ this . ringBell ( ) ;
259+ } ) ;
260+
255261 if ( props . onResize ) {
256262 this . disposableListeners . push (
257263 this . term . onResize ( ( { cols, rows} ) => {
@@ -393,47 +399,38 @@ export default class Term extends React.PureComponent<
393399 return ! e . catched ;
394400 }
395401
402+ setBellSound ( bell : string | null , sound : string | null ) {
403+ if ( bell ?. toUpperCase ( ) === 'SOUND' ) {
404+ this . bellSound = sound ? new Audio ( sound ) : this . defaultBellSound ;
405+ } else {
406+ this . bellSound = null ;
407+ }
408+ }
409+
410+ ringBell ( ) {
411+ void this . bellSound ?. play ( ) ;
412+ }
413+
396414 componentDidUpdate ( prevProps : TermProps ) {
397415 if ( ! prevProps . cleared && this . props . cleared ) {
398416 this . clear ( ) ;
399417 }
400418
401419 const nextTermOptions = getTermOptions ( this . props ) ;
402420
403- // Use bellSound in nextProps if it exists
404- // otherwise use the default sound found in xterm.
405- nextTermOptions . bellSound = this . props . bellSound || this . termDefaultBellSound ! ;
421+ if ( prevProps . bell !== this . props . bell || prevProps . bellSound !== this . props . bellSound ) {
422+ this . setBellSound ( this . props . bell , this . props . bellSound ) ;
423+ }
406424
407425 if ( prevProps . search && ! this . props . search ) {
408426 this . closeSearchBox ( ) ;
409427 }
410428
411429 // Update only options that have changed.
412- ObjectTypedKeys ( nextTermOptions )
413- . filter ( ( option ) => option !== 'theme' && nextTermOptions [ option ] !== this . termOptions [ option ] )
414- . forEach ( ( option ) => {
415- try {
416- this . term . setOption ( option , nextTermOptions [ option ] ) ;
417- } catch ( _e ) {
418- const e = _e as { message : string } ;
419- if ( / T h e w e b g l r e n d e r e r o n l y w o r k s w i t h t h e w e b g l c h a r a t l a s / i. test ( e . message ) ) {
420- // Ignore this because the char atlas will also be changed
421- } else {
422- throw e ;
423- }
424- }
425- } ) ;
426-
427- // Do we need to update theme?
428- const shouldUpdateTheme =
429- ! this . termOptions . theme ||
430- nextTermOptions . rendererType !== this . termOptions . rendererType ||
431- ObjectTypedKeys ( nextTermOptions . theme ! ) . some (
432- ( option ) => nextTermOptions . theme ! [ option ] !== this . termOptions . theme ! [ option ]
433- ) ;
434- if ( shouldUpdateTheme ) {
435- this . term . setOption ( 'theme' , nextTermOptions . theme ) ;
436- }
430+ this . term . options = pickBy (
431+ nextTermOptions ,
432+ ( value , key ) => ! isEqual ( this . termOptions [ key as keyof ITerminalOptions ] , value )
433+ ) ;
437434
438435 this . termOptions = nextTermOptions ;
439436
0 commit comments