1+ /**
2+ * Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it
3+ * possible to indent/unindent multiple lines using Tab/Shift+Tab
4+ */
5+ codeInput . plugins . Indent = class extends codeInput . Plugin {
6+ constructor ( ) {
7+ super ( ) ;
8+ }
9+
10+ /* Add keystroke events */
11+ afterElementsAdded ( codeInput ) {
12+ codeInput . check_tab = this . check_tab ;
13+ codeInput . check_enter = this . check_enter ;
14+ codeInput . querySelector ( "textarea" ) . setAttribute ( "onkeydown" , "this.parentElement.check_tab(event); this.parentElement.check_enter(event);" ) ;
15+ }
16+
17+ /* Event handlers */
18+ check_tab ( event ) {
19+ if ( event . key != "Tab" ) {
20+ return ;
21+ }
22+ let input_element = this . querySelector ( "textarea" ) ;
23+ let code = input_element . value ;
24+ event . preventDefault ( ) ; // stop normal
25+
26+ if ( ! event . shiftKey && input_element . selectionStart == input_element . selectionEnd ) {
27+ // Shift always means dedent - this places a tab here.
28+ let before_selection = code . slice ( 0 , input_element . selectionStart ) ; // text before tab
29+ let after_selection = code . slice ( input_element . selectionEnd , input_element . value . length ) ; // text after tab
30+
31+ let cursor_pos = input_element . selectionEnd + 1 ; // where cursor moves after tab - moving forward by 1 char to after tab
32+ input_element . value = before_selection + "\t" + after_selection ; // add tab char
33+
34+ // move cursor
35+ input_element . selectionStart = cursor_pos ;
36+ input_element . selectionEnd = cursor_pos ;
37+
38+ } else {
39+ let lines = input_element . value . split ( "\n" ) ;
40+ let letter_i = 0 ;
41+
42+ let selection_start = input_element . selectionStart ; // where cursor moves after tab - moving forward by 1 indent
43+ let selection_end = input_element . selectionEnd ; // where cursor moves after tab - moving forward by 1 indent
44+
45+ let number_indents = 0 ;
46+ let first_line_indents = 0 ;
47+
48+ for ( let i = 0 ; i < lines . length ; i ++ ) {
49+ letter_i += lines [ i ] . length + 1 ; // newline counted
50+
51+ console . log ( lines [ i ] , ": start" , input_element . selectionStart , letter_i , "&& end" , input_element . selectionEnd , letter_i - lines [ i ] . length )
52+ if ( input_element . selectionStart <= letter_i && input_element . selectionEnd >= letter_i - lines [ i ] . length ) {
53+ // Starts before or at last char and ends after or at first char
54+ if ( event . shiftKey ) {
55+ if ( lines [ i ] [ 0 ] == "\t" ) {
56+ // Remove first tab
57+ lines [ i ] = lines [ i ] . slice ( 1 ) ;
58+ if ( number_indents == 0 ) first_line_indents -- ;
59+ number_indents -- ;
60+ }
61+ } else {
62+ lines [ i ] = "\t" + lines [ i ] ;
63+ if ( number_indents == 0 ) first_line_indents ++ ;
64+ number_indents ++ ;
65+ }
66+
67+ }
68+ }
69+ input_element . value = lines . join ( "\n" ) ;
70+
71+ // move cursor
72+ input_element . selectionStart = selection_start + first_line_indents ;
73+ input_element . selectionEnd = selection_end + number_indents ;
74+ }
75+
76+ this . update ( input_element . value ) ;
77+ }
78+
79+ check_enter ( event ) {
80+ if ( event . key != "Enter" ) {
81+ return ;
82+ }
83+ event . preventDefault ( ) ; // stop normal
84+
85+ let input_element = this . querySelector ( "textarea" ) ;
86+ let lines = input_element . value . split ( "\n" ) ;
87+ let letter_i = 0 ;
88+ let current_line = lines . length - 1 ;
89+ let new_line = "" ;
90+ let number_indents = 0 ;
91+
92+ // find the index of the line our cursor is currently on
93+ for ( let i = 0 ; i < lines . length ; i ++ ) {
94+ letter_i += lines [ i ] . length + 1 ;
95+ if ( input_element . selectionEnd <= letter_i ) {
96+ current_line = i ;
97+ break ;
98+ }
99+ }
100+
101+ // count the number of indents the current line starts with (up to our cursor position in the line)
102+ let cursor_pos_in_line = lines [ current_line ] . length - ( letter_i - input_element . selectionEnd ) + 1 ;
103+ for ( let i = 0 ; i < cursor_pos_in_line ; i ++ ) {
104+ if ( lines [ current_line ] [ i ] == "\t" ) {
105+ number_indents ++ ;
106+ } else {
107+ break ;
108+ }
109+ }
110+
111+ // determine the text before and after the cursor and chop the current line at the new line break
112+ let text_after_cursor = "" ;
113+ if ( cursor_pos_in_line != lines [ current_line ] . length ) {
114+ text_after_cursor = lines [ current_line ] . substring ( cursor_pos_in_line ) ;
115+ lines [ current_line ] = lines [ current_line ] . substring ( 0 , cursor_pos_in_line ) ;
116+ }
117+
118+ // insert our indents and any text from the previous line that might have been after the line break
119+ for ( let i = 0 ; i < number_indents ; i ++ ) {
120+ new_line += "\t" ;
121+ }
122+ new_line += text_after_cursor ;
123+
124+ // save the current cursor position
125+ let selection_start = input_element . selectionStart ;
126+ let selection_end = input_element . selectionEnd ;
127+
128+ // splice our new line into the list of existing lines and join them all back up
129+ lines . splice ( current_line + 1 , 0 , new_line ) ;
130+ input_element . value = lines . join ( "\n" ) ;
131+
132+ // move cursor to new position
133+ input_element . selectionStart = selection_start + number_indents + 1 ; // count the indent level and the newline character
134+ input_element . selectionEnd = selection_end + number_indents + 1 ;
135+
136+ this . update ( input_element . value ) ;
137+ }
138+ }
0 commit comments